summaryrefslogtreecommitdiffstats
path: root/third_party/libwebrtc/modules/desktop_capture/linux
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/libwebrtc/modules/desktop_capture/linux')
-rw-r--r--third_party/libwebrtc/modules/desktop_capture/linux/wayland/base_capturer_pipewire.cc230
-rw-r--r--third_party/libwebrtc/modules/desktop_capture/linux/wayland/base_capturer_pipewire.h92
-rw-r--r--third_party/libwebrtc/modules/desktop_capture/linux/wayland/egl_dmabuf.cc781
-rw-r--r--third_party/libwebrtc/modules/desktop_capture/linux/wayland/egl_dmabuf.h74
-rw-r--r--third_party/libwebrtc/modules/desktop_capture/linux/wayland/mouse_cursor_monitor_pipewire.cc59
-rw-r--r--third_party/libwebrtc/modules/desktop_capture/linux/wayland/mouse_cursor_monitor_pipewire.h44
-rw-r--r--third_party/libwebrtc/modules/desktop_capture/linux/wayland/portal_request_response.h17
-rw-r--r--third_party/libwebrtc/modules/desktop_capture/linux/wayland/restore_token_manager.cc37
-rw-r--r--third_party/libwebrtc/modules/desktop_capture/linux/wayland/restore_token_manager.h46
-rw-r--r--third_party/libwebrtc/modules/desktop_capture/linux/wayland/scoped_glib.h17
-rw-r--r--third_party/libwebrtc/modules/desktop_capture/linux/wayland/screen_capture_portal_interface.cc127
-rw-r--r--third_party/libwebrtc/modules/desktop_capture/linux/wayland/screen_capture_portal_interface.h76
-rw-r--r--third_party/libwebrtc/modules/desktop_capture/linux/wayland/screencast_portal.cc471
-rw-r--r--third_party/libwebrtc/modules/desktop_capture/linux/wayland/screencast_portal.h218
-rw-r--r--third_party/libwebrtc/modules/desktop_capture/linux/wayland/screencast_stream_utils.cc123
-rw-r--r--third_party/libwebrtc/modules/desktop_capture/linux/wayland/screencast_stream_utils.h51
-rw-r--r--third_party/libwebrtc/modules/desktop_capture/linux/wayland/shared_screencast_stream.cc965
-rw-r--r--third_party/libwebrtc/modules/desktop_capture/linux/wayland/shared_screencast_stream.h95
-rw-r--r--third_party/libwebrtc/modules/desktop_capture/linux/wayland/shared_screencast_stream_unittest.cc160
-rw-r--r--third_party/libwebrtc/modules/desktop_capture/linux/wayland/test/test_screencast_stream_provider.cc361
-rw-r--r--third_party/libwebrtc/modules/desktop_capture/linux/wayland/test/test_screencast_stream_provider.h93
-rw-r--r--third_party/libwebrtc/modules/desktop_capture/linux/wayland/xdg_desktop_portal_utils.h17
-rw-r--r--third_party/libwebrtc/modules/desktop_capture/linux/wayland/xdg_session_details.h17
-rw-r--r--third_party/libwebrtc/modules/desktop_capture/linux/x11/mouse_cursor_monitor_x11.cc258
-rw-r--r--third_party/libwebrtc/modules/desktop_capture/linux/x11/mouse_cursor_monitor_x11.h68
-rw-r--r--third_party/libwebrtc/modules/desktop_capture/linux/x11/screen_capturer_x11.cc517
-rw-r--r--third_party/libwebrtc/modules/desktop_capture/linux/x11/screen_capturer_x11.h147
-rw-r--r--third_party/libwebrtc/modules/desktop_capture/linux/x11/shared_x_display.cc108
-rw-r--r--third_party/libwebrtc/modules/desktop_capture/linux/x11/shared_x_display.h88
-rw-r--r--third_party/libwebrtc/modules/desktop_capture/linux/x11/window_capturer_x11.cc256
-rw-r--r--third_party/libwebrtc/modules/desktop_capture/linux/x11/window_capturer_x11.h78
-rw-r--r--third_party/libwebrtc/modules/desktop_capture/linux/x11/window_finder_x11.cc52
-rw-r--r--third_party/libwebrtc/modules/desktop_capture/linux/x11/window_finder_x11.h35
-rw-r--r--third_party/libwebrtc/modules/desktop_capture/linux/x11/window_list_utils.cc198
-rw-r--r--third_party/libwebrtc/modules/desktop_capture/linux/x11/window_list_utils.h56
-rw-r--r--third_party/libwebrtc/modules/desktop_capture/linux/x11/x_atom_cache.cc51
-rw-r--r--third_party/libwebrtc/modules/desktop_capture/linux/x11/x_atom_cache.h45
-rw-r--r--third_party/libwebrtc/modules/desktop_capture/linux/x11/x_error_trap.cc70
-rw-r--r--third_party/libwebrtc/modules/desktop_capture/linux/x11/x_error_trap.h51
-rw-r--r--third_party/libwebrtc/modules/desktop_capture/linux/x11/x_server_pixel_buffer.cc379
-rw-r--r--third_party/libwebrtc/modules/desktop_capture/linux/x11/x_server_pixel_buffer.h89
-rw-r--r--third_party/libwebrtc/modules/desktop_capture/linux/x11/x_window_property.cc43
-rw-r--r--third_party/libwebrtc/modules/desktop_capture/linux/x11/x_window_property.h63
43 files changed, 6823 insertions, 0 deletions
diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/wayland/base_capturer_pipewire.cc b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/base_capturer_pipewire.cc
new file mode 100644
index 0000000000..cf4f7dc9aa
--- /dev/null
+++ b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/base_capturer_pipewire.cc
@@ -0,0 +1,230 @@
+/*
+ * 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/wayland/base_capturer_pipewire.h"
+
+#include "modules/desktop_capture/desktop_capture_options.h"
+#include "modules/desktop_capture/desktop_capturer.h"
+#include "modules/desktop_capture/linux/wayland/restore_token_manager.h"
+#include "modules/portal/pipewire_utils.h"
+#include "modules/portal/xdg_desktop_portal_utils.h"
+#include "rtc_base/checks.h"
+#include "rtc_base/logging.h"
+#include "rtc_base/time_utils.h"
+#include "rtc_base/trace_event.h"
+
+namespace webrtc {
+
+namespace {
+
+using xdg_portal::RequestResponse;
+using xdg_portal::ScreenCapturePortalInterface;
+using xdg_portal::SessionDetails;
+
+} // namespace
+
+// static
+bool BaseCapturerPipeWire::IsSupported() {
+ // Unfortunately, the best way we have to check if PipeWire is available is
+ // to try to initialize it.
+ // InitializePipeWire should prevent us from repeatedly initializing PipeWire,
+ // but we also don't really expect support to change without the application
+ // restarting.
+ static bool supported =
+ DesktopCapturer::IsRunningUnderWayland() && InitializePipeWire();
+ return supported;
+}
+
+BaseCapturerPipeWire::BaseCapturerPipeWire(const DesktopCaptureOptions& options,
+ CaptureType type)
+ : BaseCapturerPipeWire(options,
+ std::make_unique<ScreenCastPortal>(type, this)) {
+ is_screencast_portal_ = true;
+}
+
+BaseCapturerPipeWire::BaseCapturerPipeWire(
+ const DesktopCaptureOptions& options,
+ std::unique_ptr<ScreenCapturePortalInterface> portal)
+ : options_(options),
+ is_screencast_portal_(false),
+ portal_(std::move(portal)) {
+ source_id_ = RestoreTokenManager::GetInstance().GetUnusedId();
+ options_.screencast_stream()->SetUseDamageRegion(
+ options_.pipewire_use_damage_region());
+}
+
+BaseCapturerPipeWire::~BaseCapturerPipeWire() {
+ options_.screencast_stream()->StopScreenCastStream();
+}
+
+void BaseCapturerPipeWire::OnScreenCastRequestResult(RequestResponse result,
+ uint32_t stream_node_id,
+ int fd) {
+ is_portal_open_ = false;
+
+ // Reset the value of capturer_failed_ in case we succeed below. If we fail,
+ // then it'll set it to the right value again soon enough.
+ capturer_failed_ = false;
+ if (result != RequestResponse::kSuccess ||
+ !options_.screencast_stream()->StartScreenCastStream(
+ stream_node_id, fd, options_.get_width(), options_.get_height(),
+ options_.prefer_cursor_embedded())) {
+ capturer_failed_ = true;
+ RTC_LOG(LS_ERROR) << "ScreenCastPortal failed: "
+ << static_cast<uint>(result);
+ } else if (ScreenCastPortal* screencast_portal = GetScreenCastPortal()) {
+ if (!screencast_portal->RestoreToken().empty()) {
+ RestoreTokenManager::GetInstance().AddToken(
+ source_id_, screencast_portal->RestoreToken());
+ }
+ }
+
+ if (!delegated_source_list_observer_)
+ return;
+
+ switch (result) {
+ case RequestResponse::kUnknown:
+ RTC_DCHECK_NOTREACHED();
+ break;
+ case RequestResponse::kSuccess:
+ delegated_source_list_observer_->OnSelection();
+ break;
+ case RequestResponse::kUserCancelled:
+ delegated_source_list_observer_->OnCancelled();
+ break;
+ case RequestResponse::kError:
+ delegated_source_list_observer_->OnError();
+ break;
+ }
+}
+
+void BaseCapturerPipeWire::OnScreenCastSessionClosed() {
+ if (!capturer_failed_) {
+ options_.screencast_stream()->StopScreenCastStream();
+ }
+}
+
+void BaseCapturerPipeWire::UpdateResolution(uint32_t width, uint32_t height) {
+ if (!capturer_failed_) {
+ options_.screencast_stream()->UpdateScreenCastStreamResolution(width,
+ height);
+ }
+}
+
+void BaseCapturerPipeWire::Start(Callback* callback) {
+ RTC_DCHECK(!callback_);
+ RTC_DCHECK(callback);
+
+ callback_ = callback;
+
+ if (ScreenCastPortal* screencast_portal = GetScreenCastPortal()) {
+ screencast_portal->SetPersistMode(
+ ScreenCastPortal::PersistMode::kTransient);
+ if (selected_source_id_) {
+ screencast_portal->SetRestoreToken(
+ RestoreTokenManager::GetInstance().TakeToken(selected_source_id_));
+ }
+ }
+
+ is_portal_open_ = true;
+ portal_->Start();
+}
+
+void BaseCapturerPipeWire::CaptureFrame() {
+ TRACE_EVENT0("webrtc", "BaseCapturerPipeWire::CaptureFrame");
+ if (capturer_failed_) {
+ // This could be recoverable if the source list is re-summoned; but for our
+ // purposes this is fine, since it requires intervention to resolve and
+ // essentially starts a new capture.
+ callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr);
+ return;
+ }
+
+ int64_t capture_start_time_nanos = rtc::TimeNanos();
+ std::unique_ptr<DesktopFrame> frame =
+ options_.screencast_stream()->CaptureFrame();
+
+ if (!frame || !frame->data()) {
+ callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr);
+ return;
+ }
+
+ // TODO(julien.isorce): http://crbug.com/945468. Set the icc profile on
+ // the frame, see ScreenCapturerX11::CaptureFrame.
+
+ frame->set_capturer_id(DesktopCapturerId::kWaylandCapturerLinux);
+ frame->set_capture_time_ms((rtc::TimeNanos() - capture_start_time_nanos) /
+ rtc::kNumNanosecsPerMillisec);
+ callback_->OnCaptureResult(Result::SUCCESS, std::move(frame));
+}
+
+bool BaseCapturerPipeWire::GetSourceList(SourceList* sources) {
+ RTC_DCHECK(sources->size() == 0);
+ // List of available screens is already presented by the xdg-desktop-portal,
+ // so we just need a (valid) source id for any callers to pass around, even
+ // though it doesn't mean anything to us. Until the user selects a source in
+ // xdg-desktop-portal we'll just end up returning empty frames. Note that "0"
+ // is often treated as a null/placeholder id, so we shouldn't use that.
+ // TODO(https://crbug.com/1297671): Reconsider type of ID when plumbing
+ // token that will enable stream re-use.
+ sources->push_back({source_id_});
+ return true;
+}
+
+bool BaseCapturerPipeWire::SelectSource(SourceId id) {
+ // Screen selection is handled by the xdg-desktop-portal.
+ selected_source_id_ = id;
+ return true;
+}
+
+DelegatedSourceListController*
+BaseCapturerPipeWire::GetDelegatedSourceListController() {
+ return this;
+}
+
+void BaseCapturerPipeWire::Observe(Observer* observer) {
+ RTC_DCHECK(!delegated_source_list_observer_ || !observer);
+ delegated_source_list_observer_ = observer;
+}
+
+void BaseCapturerPipeWire::EnsureVisible() {
+ RTC_DCHECK(callback_);
+ if (is_portal_open_)
+ return;
+
+ // Clear any previously selected state/capture
+ portal_->Stop();
+ options_.screencast_stream()->StopScreenCastStream();
+
+ // Get a new source id to reflect that the source has changed.
+ source_id_ = RestoreTokenManager::GetInstance().GetUnusedId();
+
+ is_portal_open_ = true;
+ portal_->Start();
+}
+
+void BaseCapturerPipeWire::EnsureHidden() {
+ if (!is_portal_open_)
+ return;
+
+ is_portal_open_ = false;
+ portal_->Stop();
+}
+
+SessionDetails BaseCapturerPipeWire::GetSessionDetails() {
+ return portal_->GetSessionDetails();
+}
+
+ScreenCastPortal* BaseCapturerPipeWire::GetScreenCastPortal() {
+ return is_screencast_portal_ ? static_cast<ScreenCastPortal*>(portal_.get())
+ : nullptr;
+}
+
+} // namespace webrtc
diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/wayland/base_capturer_pipewire.h b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/base_capturer_pipewire.h
new file mode 100644
index 0000000000..4b5cdc4a65
--- /dev/null
+++ b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/base_capturer_pipewire.h
@@ -0,0 +1,92 @@
+/*
+ * 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_WAYLAND_BASE_CAPTURER_PIPEWIRE_H_
+#define MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_BASE_CAPTURER_PIPEWIRE_H_
+
+#include "modules/desktop_capture/delegated_source_list_controller.h"
+#include "modules/desktop_capture/desktop_capture_options.h"
+#include "modules/desktop_capture/desktop_capturer.h"
+#include "modules/desktop_capture/linux/wayland/screen_capture_portal_interface.h"
+#include "modules/desktop_capture/linux/wayland/screencast_portal.h"
+#include "modules/desktop_capture/linux/wayland/shared_screencast_stream.h"
+#include "modules/portal/portal_request_response.h"
+#include "modules/portal/xdg_desktop_portal_utils.h"
+#include "modules/portal/xdg_session_details.h"
+
+namespace webrtc {
+
+class RTC_EXPORT BaseCapturerPipeWire
+ : public DesktopCapturer,
+ public DelegatedSourceListController,
+ public ScreenCastPortal::PortalNotifier {
+ public:
+ // Returns whether or not the current system can support capture via PipeWire.
+ // This will only be true on Wayland systems that also have PipeWire
+ // available, and thus may require dlopening PipeWire to determine if it is
+ // available.
+ static bool IsSupported();
+
+ BaseCapturerPipeWire(const DesktopCaptureOptions& options, CaptureType type);
+ BaseCapturerPipeWire(
+ const DesktopCaptureOptions& options,
+ std::unique_ptr<xdg_portal::ScreenCapturePortalInterface> portal);
+ ~BaseCapturerPipeWire() override;
+
+ BaseCapturerPipeWire(const BaseCapturerPipeWire&) = delete;
+ BaseCapturerPipeWire& operator=(const BaseCapturerPipeWire&) = delete;
+
+ // DesktopCapturer interface.
+ void Start(Callback* delegate) override;
+ void CaptureFrame() override;
+ bool GetSourceList(SourceList* sources) override;
+ bool SelectSource(SourceId id) override;
+ DelegatedSourceListController* GetDelegatedSourceListController() override;
+
+ // DelegatedSourceListController
+ void Observe(Observer* observer) override;
+ void EnsureVisible() override;
+ void EnsureHidden() override;
+
+ // ScreenCastPortal::PortalNotifier interface.
+ void OnScreenCastRequestResult(xdg_portal::RequestResponse result,
+ uint32_t stream_node_id,
+ int fd) override;
+ void OnScreenCastSessionClosed() override;
+ void UpdateResolution(uint32_t width, uint32_t height) override;
+
+ xdg_portal::SessionDetails GetSessionDetails();
+
+ private:
+ ScreenCastPortal* GetScreenCastPortal();
+
+ DesktopCaptureOptions options_ = {};
+ Callback* callback_ = nullptr;
+ bool capturer_failed_ = false;
+ bool is_screencast_portal_ = false;
+ bool is_portal_open_ = false;
+
+ Observer* delegated_source_list_observer_ = nullptr;
+
+ // SourceId that is selected using SelectSource() and that we previously
+ // returned in GetSourceList(). This should be a SourceId that has a restore
+ // token associated with it and can be restored if we have required version
+ // of xdg-desktop-portal.
+ SourceId selected_source_id_ = 0;
+ // SourceID we randomly generate and that is returned in GetSourceList() as
+ // available source that will later get assigned to a restore token in order
+ // to be restored later using SelectSource().
+ SourceId source_id_ = 0;
+ std::unique_ptr<xdg_portal::ScreenCapturePortalInterface> portal_;
+};
+
+} // namespace webrtc
+
+#endif // MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_BASE_CAPTURER_PIPEWIRE_H_
diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/wayland/egl_dmabuf.cc b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/egl_dmabuf.cc
new file mode 100644
index 0000000000..6a019c64b4
--- /dev/null
+++ b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/egl_dmabuf.cc
@@ -0,0 +1,781 @@
+/*
+ * Copyright 2021 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "modules/desktop_capture/linux/wayland/egl_dmabuf.h"
+
+#include <asm/ioctl.h>
+#include <dlfcn.h>
+#include <fcntl.h>
+#include <gdk/gdk.h>
+#include <libdrm/drm_fourcc.h>
+#include <linux/types.h>
+#include <spa/param/video/format-utils.h>
+#include <unistd.h>
+#include <xf86drm.h>
+
+#include "absl/memory/memory.h"
+#include "absl/types/optional.h"
+#include "rtc_base/checks.h"
+#include "rtc_base/logging.h"
+#include "rtc_base/sanitizer.h"
+#include "rtc_base/string_encode.h"
+
+namespace webrtc {
+
+// EGL
+typedef EGLBoolean (*eglBindAPI_func)(EGLenum api);
+typedef EGLContext (*eglCreateContext_func)(EGLDisplay dpy,
+ EGLConfig config,
+ EGLContext share_context,
+ const EGLint* attrib_list);
+typedef EGLBoolean (*eglDestroyContext_func)(EGLDisplay display,
+ EGLContext context);
+typedef EGLBoolean (*eglTerminate_func)(EGLDisplay display);
+typedef EGLImageKHR (*eglCreateImageKHR_func)(EGLDisplay dpy,
+ EGLContext ctx,
+ EGLenum target,
+ EGLClientBuffer buffer,
+ const EGLint* attrib_list);
+typedef EGLBoolean (*eglDestroyImageKHR_func)(EGLDisplay dpy,
+ EGLImageKHR image);
+typedef EGLint (*eglGetError_func)(void);
+typedef void* (*eglGetProcAddress_func)(const char*);
+typedef EGLDisplay (*eglGetPlatformDisplayEXT_func)(EGLenum platform,
+ void* native_display,
+ const EGLint* attrib_list);
+typedef EGLDisplay (*eglGetPlatformDisplay_func)(EGLenum platform,
+ void* native_display,
+ const EGLAttrib* attrib_list);
+
+typedef EGLBoolean (*eglInitialize_func)(EGLDisplay dpy,
+ EGLint* major,
+ EGLint* minor);
+typedef EGLBoolean (*eglMakeCurrent_func)(EGLDisplay dpy,
+ EGLSurface draw,
+ EGLSurface read,
+ EGLContext ctx);
+typedef EGLBoolean (*eglQueryDmaBufFormatsEXT_func)(EGLDisplay dpy,
+ EGLint max_formats,
+ EGLint* formats,
+ EGLint* num_formats);
+typedef EGLBoolean (*eglQueryDmaBufModifiersEXT_func)(EGLDisplay dpy,
+ EGLint format,
+ EGLint max_modifiers,
+ EGLuint64KHR* modifiers,
+ EGLBoolean* external_only,
+ EGLint* num_modifiers);
+typedef const char* (*eglQueryString_func)(EGLDisplay dpy, EGLint name);
+typedef void (*glEGLImageTargetTexture2DOES_func)(GLenum target,
+ GLeglImageOES image);
+
+// This doesn't follow naming conventions in WebRTC, where the naming
+// should look like e.g. egl_bind_api instead of EglBindAPI, however
+// we named them according to the exported functions they map to for
+// consistency.
+eglBindAPI_func EglBindAPI = nullptr;
+eglCreateContext_func EglCreateContext = nullptr;
+eglDestroyContext_func EglDestroyContext = nullptr;
+eglTerminate_func EglTerminate = nullptr;
+eglCreateImageKHR_func EglCreateImageKHR = nullptr;
+eglDestroyImageKHR_func EglDestroyImageKHR = nullptr;
+eglGetError_func EglGetError = nullptr;
+eglGetProcAddress_func EglGetProcAddress = nullptr;
+eglGetPlatformDisplayEXT_func EglGetPlatformDisplayEXT = nullptr;
+eglGetPlatformDisplay_func EglGetPlatformDisplay = nullptr;
+eglInitialize_func EglInitialize = nullptr;
+eglMakeCurrent_func EglMakeCurrent = nullptr;
+eglQueryDmaBufFormatsEXT_func EglQueryDmaBufFormatsEXT = nullptr;
+eglQueryDmaBufModifiersEXT_func EglQueryDmaBufModifiersEXT = nullptr;
+eglQueryString_func EglQueryString = nullptr;
+glEGLImageTargetTexture2DOES_func GlEGLImageTargetTexture2DOES = nullptr;
+
+// GL
+typedef void (*glBindTexture_func)(GLenum target, GLuint texture);
+typedef void (*glDeleteTextures_func)(GLsizei n, const GLuint* textures);
+typedef void (*glGenTextures_func)(GLsizei n, GLuint* textures);
+typedef GLenum (*glGetError_func)(void);
+typedef const GLubyte* (*glGetString_func)(GLenum name);
+typedef void (*glReadPixels_func)(GLint x,
+ GLint y,
+ GLsizei width,
+ GLsizei height,
+ GLenum format,
+ GLenum type,
+ void* data);
+typedef void (*glGenFramebuffers_func)(GLsizei n, GLuint* ids);
+typedef void (*glDeleteFramebuffers_func)(GLsizei n,
+ const GLuint* framebuffers);
+typedef void (*glBindFramebuffer_func)(GLenum target, GLuint framebuffer);
+typedef void (*glFramebufferTexture2D_func)(GLenum target,
+ GLenum attachment,
+ GLenum textarget,
+ GLuint texture,
+ GLint level);
+typedef GLenum (*glCheckFramebufferStatus_func)(GLenum target);
+typedef void (*glTexParameteri_func)(GLenum target, GLenum pname, GLint param);
+typedef void* (*glXGetProcAddressARB_func)(const char*);
+
+// This doesn't follow naming conventions in WebRTC, where the naming
+// should look like e.g. egl_bind_api instead of EglBindAPI, however
+// we named them according to the exported functions they map to for
+// consistency.
+glBindTexture_func GlBindTexture = nullptr;
+glDeleteTextures_func GlDeleteTextures = nullptr;
+glGenTextures_func GlGenTextures = nullptr;
+glGetError_func GlGetError = nullptr;
+glGetString_func GlGetString = nullptr;
+glReadPixels_func GlReadPixels = nullptr;
+glGenFramebuffers_func GlGenFramebuffers = nullptr;
+glDeleteFramebuffers_func GlDeleteFramebuffers = nullptr;
+glBindFramebuffer_func GlBindFramebuffer = nullptr;
+glFramebufferTexture2D_func GlFramebufferTexture2D = nullptr;
+glCheckFramebufferStatus_func GlCheckFramebufferStatus = nullptr;
+glTexParameteri_func GlTexParameteri = nullptr;
+glXGetProcAddressARB_func GlXGetProcAddressARB = nullptr;
+
+static const std::string FormatGLError(GLenum err) {
+ switch (err) {
+ case GL_NO_ERROR:
+ return "GL_NO_ERROR";
+ case GL_INVALID_ENUM:
+ return "GL_INVALID_ENUM";
+ case GL_INVALID_VALUE:
+ return "GL_INVALID_VALUE";
+ case GL_INVALID_OPERATION:
+ return "GL_INVALID_OPERATION";
+ case GL_STACK_OVERFLOW:
+ return "GL_STACK_OVERFLOW";
+ case GL_STACK_UNDERFLOW:
+ return "GL_STACK_UNDERFLOW";
+ case GL_OUT_OF_MEMORY:
+ return "GL_OUT_OF_MEMORY";
+ default:
+ return "GL error code: " + std::to_string(err);
+ }
+}
+
+static const std::string FormatEGLError(EGLint err) {
+ switch (err) {
+ case EGL_NOT_INITIALIZED:
+ return "EGL_NOT_INITIALIZED";
+ case EGL_BAD_ACCESS:
+ return "EGL_BAD_ACCESS";
+ case EGL_BAD_ALLOC:
+ return "EGL_BAD_ALLOC";
+ case EGL_BAD_ATTRIBUTE:
+ return "EGL_BAD_ATTRIBUTE";
+ case EGL_BAD_CONTEXT:
+ return "EGL_BAD_CONTEXT";
+ case EGL_BAD_CONFIG:
+ return "EGL_BAD_CONFIG";
+ case EGL_BAD_CURRENT_SURFACE:
+ return "EGL_BAD_CURRENT_SURFACE";
+ case EGL_BAD_DISPLAY:
+ return "EGL_BAD_DISPLAY";
+ case EGL_BAD_SURFACE:
+ return "EGL_BAD_SURFACE";
+ case EGL_BAD_MATCH:
+ return "EGL_BAD_MATCH";
+ case EGL_BAD_PARAMETER:
+ return "EGL_BAD_PARAMETER";
+ case EGL_BAD_NATIVE_PIXMAP:
+ return "EGL_BAD_NATIVE_PIXMAP";
+ case EGL_BAD_NATIVE_WINDOW:
+ return "EGL_BAD_NATIVE_WINDOW";
+ case EGL_CONTEXT_LOST:
+ return "EGL_CONTEXT_LOST";
+ default:
+ return "EGL error code: " + std::to_string(err);
+ }
+}
+
+static uint32_t SpaPixelFormatToDrmFormat(uint32_t spa_format) {
+ switch (spa_format) {
+ case SPA_VIDEO_FORMAT_RGBA:
+ return DRM_FORMAT_ABGR8888;
+ case SPA_VIDEO_FORMAT_RGBx:
+ return DRM_FORMAT_XBGR8888;
+ case SPA_VIDEO_FORMAT_BGRA:
+ return DRM_FORMAT_ARGB8888;
+ case SPA_VIDEO_FORMAT_BGRx:
+ return DRM_FORMAT_XRGB8888;
+ default:
+ return DRM_FORMAT_INVALID;
+ }
+}
+
+static void CloseLibrary(void* library) {
+ if (library) {
+ dlclose(library);
+ library = nullptr;
+ }
+}
+
+static bool IsWaylandDisplay() {
+ static auto sGdkWaylandDisplayGetType =
+ (GType (*)(void))dlsym(RTLD_DEFAULT, "gdk_wayland_display_get_type");
+ if (!sGdkWaylandDisplayGetType) {
+ return false;
+ }
+ return (G_TYPE_CHECK_INSTANCE_TYPE ((gdk_display_get_default()),
+ sGdkWaylandDisplayGetType()));
+}
+
+static bool IsX11Display() {
+ static auto sGdkX11DisplayGetType =
+ (GType (*)(void))dlsym(RTLD_DEFAULT, "gdk_x11_display_get_type");
+ if (!sGdkX11DisplayGetType) {
+ return false;
+ }
+ return (G_TYPE_CHECK_INSTANCE_TYPE ((gdk_display_get_default()),
+ sGdkX11DisplayGetType()));
+}
+
+static void* g_lib_egl = nullptr;
+
+RTC_NO_SANITIZE("cfi-icall")
+static bool OpenEGL() {
+ g_lib_egl = dlopen("libEGL.so.1", RTLD_NOW | RTLD_GLOBAL);
+ if (g_lib_egl) {
+ EglGetProcAddress =
+ (eglGetProcAddress_func)dlsym(g_lib_egl, "eglGetProcAddress");
+ return EglGetProcAddress;
+ }
+
+ return false;
+}
+
+RTC_NO_SANITIZE("cfi-icall")
+static bool LoadEGL() {
+ if (OpenEGL()) {
+ EglBindAPI = (eglBindAPI_func)EglGetProcAddress("eglBindAPI");
+ EglCreateContext =
+ (eglCreateContext_func)EglGetProcAddress("eglCreateContext");
+ EglDestroyContext =
+ (eglDestroyContext_func)EglGetProcAddress("eglDestroyContext");
+ EglTerminate = (eglTerminate_func)EglGetProcAddress("eglTerminate");
+ EglCreateImageKHR =
+ (eglCreateImageKHR_func)EglGetProcAddress("eglCreateImageKHR");
+ EglDestroyImageKHR =
+ (eglDestroyImageKHR_func)EglGetProcAddress("eglDestroyImageKHR");
+ EglGetError = (eglGetError_func)EglGetProcAddress("eglGetError");
+ EglGetPlatformDisplayEXT = (eglGetPlatformDisplayEXT_func)EglGetProcAddress(
+ "eglGetPlatformDisplayEXT");
+ EglGetPlatformDisplay =
+ (eglGetPlatformDisplay_func)EglGetProcAddress("eglGetPlatformDisplay");
+ EglInitialize = (eglInitialize_func)EglGetProcAddress("eglInitialize");
+ EglMakeCurrent = (eglMakeCurrent_func)EglGetProcAddress("eglMakeCurrent");
+ EglQueryString = (eglQueryString_func)EglGetProcAddress("eglQueryString");
+ GlEGLImageTargetTexture2DOES =
+ (glEGLImageTargetTexture2DOES_func)EglGetProcAddress(
+ "glEGLImageTargetTexture2DOES");
+
+ return EglBindAPI && EglCreateContext && EglCreateImageKHR &&
+ EglTerminate && EglDestroyContext && EglDestroyImageKHR &&
+ EglGetError && EglGetPlatformDisplayEXT && EglGetPlatformDisplay &&
+ EglInitialize && EglMakeCurrent && EglQueryString &&
+ GlEGLImageTargetTexture2DOES;
+ }
+
+ return false;
+}
+
+static void* g_lib_gl = nullptr;
+
+RTC_NO_SANITIZE("cfi-icall")
+static bool OpenGL() {
+ std::vector<std::string> names = {"libGL.so.1", "libGL.so"};
+ for (const std::string& name : names) {
+ g_lib_gl = dlopen(name.c_str(), RTLD_NOW | RTLD_GLOBAL);
+ if (g_lib_gl) {
+ GlXGetProcAddressARB =
+ (glXGetProcAddressARB_func)dlsym(g_lib_gl, "glXGetProcAddressARB");
+ return GlXGetProcAddressARB;
+ }
+ }
+
+ return false;
+}
+
+RTC_NO_SANITIZE("cfi-icall")
+static bool LoadGL() {
+ if (OpenGL()) {
+ GlGetString = (glGetString_func)GlXGetProcAddressARB("glGetString");
+ if (!GlGetString) {
+ return false;
+ }
+
+ GlBindTexture = (glBindTexture_func)GlXGetProcAddressARB("glBindTexture");
+ GlDeleteTextures =
+ (glDeleteTextures_func)GlXGetProcAddressARB("glDeleteTextures");
+ GlGenTextures = (glGenTextures_func)GlXGetProcAddressARB("glGenTextures");
+ GlGetError = (glGetError_func)GlXGetProcAddressARB("glGetError");
+ GlReadPixels = (glReadPixels_func)GlXGetProcAddressARB("glReadPixels");
+ GlGenFramebuffers =
+ (glGenFramebuffers_func)GlXGetProcAddressARB("glGenFramebuffers");
+ GlDeleteFramebuffers =
+ (glDeleteFramebuffers_func)GlXGetProcAddressARB("glDeleteFramebuffers");
+ GlBindFramebuffer =
+ (glBindFramebuffer_func)GlXGetProcAddressARB("glBindFramebuffer");
+ GlFramebufferTexture2D = (glFramebufferTexture2D_func)GlXGetProcAddressARB(
+ "glFramebufferTexture2D");
+ GlCheckFramebufferStatus =
+ (glCheckFramebufferStatus_func)GlXGetProcAddressARB(
+ "glCheckFramebufferStatus");
+
+ GlTexParameteri =
+ (glTexParameteri_func)GlXGetProcAddressARB("glTexParameteri");
+
+ return GlBindTexture && GlDeleteTextures && GlGenTextures && GlGetError &&
+ GlReadPixels && GlGenFramebuffers && GlDeleteFramebuffers &&
+ GlBindFramebuffer && GlFramebufferTexture2D &&
+ GlCheckFramebufferStatus && GlTexParameteri;
+ }
+
+ return false;
+}
+
+RTC_NO_SANITIZE("cfi-icall")
+EglDmaBuf::EglDmaBuf() {
+ if (!LoadEGL()) {
+ RTC_LOG(LS_ERROR) << "Unable to load EGL entry functions.";
+ CloseLibrary(g_lib_egl);
+ return;
+ }
+
+ if (!LoadGL()) {
+ RTC_LOG(LS_ERROR) << "Failed to load OpenGL entry functions.";
+ CloseLibrary(g_lib_gl);
+ return;
+ }
+
+ if (!GetClientExtensions(EGL_NO_DISPLAY, EGL_EXTENSIONS)) {
+ return;
+ }
+
+ bool has_platform_base_ext = false;
+ bool has_platform_gbm_ext = false;
+ bool has_khr_platform_gbm_ext = false;
+
+ for (const auto& extension : egl_.extensions) {
+ if (extension == "EGL_EXT_platform_base") {
+ has_platform_base_ext = true;
+ continue;
+ } else if (extension == "EGL_MESA_platform_gbm") {
+ has_platform_gbm_ext = true;
+ continue;
+ } else if (extension == "EGL_KHR_platform_gbm") {
+ has_khr_platform_gbm_ext = true;
+ continue;
+ }
+ }
+
+ if (!has_platform_base_ext || !has_platform_gbm_ext ||
+ !has_khr_platform_gbm_ext) {
+ RTC_LOG(LS_ERROR) << "One of required EGL extensions is missing";
+ return;
+ }
+
+ if (IsWaylandDisplay()) {
+ egl_.display = EglGetPlatformDisplay(EGL_PLATFORM_WAYLAND_KHR,
+ (void*)EGL_DEFAULT_DISPLAY, nullptr);
+ } else if (IsX11Display()) {
+ egl_.display = EglGetPlatformDisplay(EGL_PLATFORM_X11_KHR,
+ (void*)EGL_DEFAULT_DISPLAY, nullptr);
+ }
+
+ if (egl_.display == EGL_NO_DISPLAY) {
+ RTC_LOG(LS_ERROR) << "Failed to obtain default EGL display: "
+ << FormatEGLError(EglGetError()) << "\n"
+ << "Defaulting to using first available render node";
+ absl::optional<std::string> render_node = GetRenderNode();
+ if (!render_node) {
+ return;
+ }
+
+ drm_fd_ = open(render_node->c_str(), O_RDWR);
+
+ if (drm_fd_ < 0) {
+ RTC_LOG(LS_ERROR) << "Failed to open drm render node: "
+ << strerror(errno);
+ return;
+ }
+
+ gbm_device_ = gbm_create_device(drm_fd_);
+
+ if (!gbm_device_) {
+ RTC_LOG(LS_ERROR) << "Cannot create GBM device: " << strerror(errno);
+ close(drm_fd_);
+ return;
+ }
+
+ // Use eglGetPlatformDisplayEXT() to get the display pointer
+ // if the implementation supports it.
+ egl_.display =
+ EglGetPlatformDisplayEXT(EGL_PLATFORM_GBM_KHR, gbm_device_, nullptr);
+ }
+
+ if (egl_.display == EGL_NO_DISPLAY) {
+ RTC_LOG(LS_ERROR) << "Error during obtaining EGL display: "
+ << FormatEGLError(EglGetError());
+ return;
+ }
+
+ EGLint major, minor;
+ if (EglInitialize(egl_.display, &major, &minor) == EGL_FALSE) {
+ RTC_LOG(LS_ERROR) << "Error during eglInitialize: "
+ << FormatEGLError(EglGetError());
+ return;
+ }
+
+ if (EglBindAPI(EGL_OPENGL_API) == EGL_FALSE) {
+ RTC_LOG(LS_ERROR) << "bind OpenGL API failed";
+ return;
+ }
+
+ egl_.context =
+ EglCreateContext(egl_.display, nullptr, EGL_NO_CONTEXT, nullptr);
+
+ if (egl_.context == EGL_NO_CONTEXT) {
+ RTC_LOG(LS_ERROR) << "Couldn't create EGL context: "
+ << FormatGLError(EglGetError());
+ return;
+ }
+
+ if (!GetClientExtensions(egl_.display, EGL_EXTENSIONS)) {
+ return;
+ }
+
+ bool has_image_dma_buf_import_modifiers_ext = false;
+
+ for (const auto& extension : egl_.extensions) {
+ if (extension == "EGL_EXT_image_dma_buf_import") {
+ has_image_dma_buf_import_ext_ = true;
+ continue;
+ } else if (extension == "EGL_EXT_image_dma_buf_import_modifiers") {
+ has_image_dma_buf_import_modifiers_ext = true;
+ continue;
+ }
+ }
+
+ if (has_image_dma_buf_import_ext_ && has_image_dma_buf_import_modifiers_ext) {
+ EglQueryDmaBufFormatsEXT = (eglQueryDmaBufFormatsEXT_func)EglGetProcAddress(
+ "eglQueryDmaBufFormatsEXT");
+ EglQueryDmaBufModifiersEXT =
+ (eglQueryDmaBufModifiersEXT_func)EglGetProcAddress(
+ "eglQueryDmaBufModifiersEXT");
+ }
+
+ RTC_LOG(LS_INFO) << "Egl initialization succeeded";
+ egl_initialized_ = true;
+}
+
+RTC_NO_SANITIZE("cfi-icall")
+EglDmaBuf::~EglDmaBuf() {
+ if (gbm_device_) {
+ gbm_device_destroy(gbm_device_);
+ close(drm_fd_);
+ }
+
+ if (egl_.context != EGL_NO_CONTEXT) {
+ EglDestroyContext(egl_.display, egl_.context);
+ }
+
+ if (egl_.display != EGL_NO_DISPLAY) {
+ EglTerminate(egl_.display);
+ }
+
+ if (fbo_) {
+ GlDeleteFramebuffers(1, &fbo_);
+ }
+
+ if (texture_) {
+ GlDeleteTextures(1, &texture_);
+ }
+
+ // BUG: crbug.com/1290566
+ // Closing libEGL.so.1 when using NVidia drivers causes a crash
+ // when EglGetPlatformDisplayEXT() is used, at least this one is enough
+ // to be called to make it crash.
+ // It also looks that libepoxy and glad don't dlclose it either
+ // CloseLibrary(g_lib_egl);
+ // CloseLibrary(g_lib_gl);
+}
+
+RTC_NO_SANITIZE("cfi-icall")
+bool EglDmaBuf::GetClientExtensions(EGLDisplay dpy, EGLint name) {
+ // Get the list of client extensions
+ const char* client_extensions_cstring = EglQueryString(dpy, name);
+ if (!client_extensions_cstring) {
+ // If eglQueryString() returned NULL, the implementation doesn't support
+ // EGL_EXT_client_extensions. Expect an EGL_BAD_DISPLAY error.
+ RTC_LOG(LS_ERROR) << "No client extensions defined! "
+ << FormatEGLError(EglGetError());
+ return false;
+ }
+
+ std::vector<absl::string_view> client_extensions =
+ rtc::split(client_extensions_cstring, ' ');
+ for (const auto& extension : client_extensions) {
+ egl_.extensions.push_back(std::string(extension));
+ }
+
+ return true;
+}
+
+RTC_NO_SANITIZE("cfi-icall")
+bool EglDmaBuf::ImageFromDmaBuf(const DesktopSize& size,
+ uint32_t format,
+ const std::vector<PlaneData>& plane_datas,
+ uint64_t modifier,
+ const DesktopVector& offset,
+ const DesktopSize& buffer_size,
+ uint8_t* data) {
+ if (!egl_initialized_) {
+ return false;
+ }
+
+ if (plane_datas.size() <= 0) {
+ RTC_LOG(LS_ERROR) << "Failed to process buffer: invalid number of planes";
+ return false;
+ }
+
+ EGLint attribs[47];
+ int atti = 0;
+
+ attribs[atti++] = EGL_WIDTH;
+ attribs[atti++] = static_cast<EGLint>(size.width());
+ attribs[atti++] = EGL_HEIGHT;
+ attribs[atti++] = static_cast<EGLint>(size.height());
+ attribs[atti++] = EGL_LINUX_DRM_FOURCC_EXT;
+ attribs[atti++] = SpaPixelFormatToDrmFormat(format);
+
+ if (plane_datas.size() > 0) {
+ attribs[atti++] = EGL_DMA_BUF_PLANE0_FD_EXT;
+ attribs[atti++] = plane_datas[0].fd;
+ attribs[atti++] = EGL_DMA_BUF_PLANE0_OFFSET_EXT;
+ attribs[atti++] = plane_datas[0].offset;
+ attribs[atti++] = EGL_DMA_BUF_PLANE0_PITCH_EXT;
+ attribs[atti++] = plane_datas[0].stride;
+
+ if (modifier != DRM_FORMAT_MOD_INVALID) {
+ attribs[atti++] = EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT;
+ attribs[atti++] = modifier & 0xFFFFFFFF;
+ attribs[atti++] = EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT;
+ attribs[atti++] = modifier >> 32;
+ }
+ }
+
+ if (plane_datas.size() > 1) {
+ attribs[atti++] = EGL_DMA_BUF_PLANE1_FD_EXT;
+ attribs[atti++] = plane_datas[1].fd;
+ attribs[atti++] = EGL_DMA_BUF_PLANE1_OFFSET_EXT;
+ attribs[atti++] = plane_datas[1].offset;
+ attribs[atti++] = EGL_DMA_BUF_PLANE1_PITCH_EXT;
+ attribs[atti++] = plane_datas[1].stride;
+
+ if (modifier != DRM_FORMAT_MOD_INVALID) {
+ attribs[atti++] = EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT;
+ attribs[atti++] = modifier & 0xFFFFFFFF;
+ attribs[atti++] = EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT;
+ attribs[atti++] = modifier >> 32;
+ }
+ }
+
+ if (plane_datas.size() > 2) {
+ attribs[atti++] = EGL_DMA_BUF_PLANE2_FD_EXT;
+ attribs[atti++] = plane_datas[2].fd;
+ attribs[atti++] = EGL_DMA_BUF_PLANE2_OFFSET_EXT;
+ attribs[atti++] = plane_datas[2].offset;
+ attribs[atti++] = EGL_DMA_BUF_PLANE2_PITCH_EXT;
+ attribs[atti++] = plane_datas[2].stride;
+
+ if (modifier != DRM_FORMAT_MOD_INVALID) {
+ attribs[atti++] = EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT;
+ attribs[atti++] = modifier & 0xFFFFFFFF;
+ attribs[atti++] = EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT;
+ attribs[atti++] = modifier >> 32;
+ }
+ }
+
+ if (plane_datas.size() > 3) {
+ attribs[atti++] = EGL_DMA_BUF_PLANE3_FD_EXT;
+ attribs[atti++] = plane_datas[3].fd;
+ attribs[atti++] = EGL_DMA_BUF_PLANE3_OFFSET_EXT;
+ attribs[atti++] = plane_datas[3].offset;
+ attribs[atti++] = EGL_DMA_BUF_PLANE3_PITCH_EXT;
+ attribs[atti++] = plane_datas[3].stride;
+
+ if (modifier != DRM_FORMAT_MOD_INVALID) {
+ attribs[atti++] = EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT;
+ attribs[atti++] = modifier & 0xFFFFFFFF;
+ attribs[atti++] = EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT;
+ attribs[atti++] = modifier >> 32;
+ }
+ }
+
+ attribs[atti++] = EGL_NONE;
+
+ // bind context to render thread
+ EglMakeCurrent(egl_.display, EGL_NO_SURFACE, EGL_NO_SURFACE, egl_.context);
+
+ // create EGL image from attribute list
+ EGLImageKHR image = EglCreateImageKHR(
+ egl_.display, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, nullptr, attribs);
+
+ if (image == EGL_NO_IMAGE) {
+ RTC_LOG(LS_ERROR) << "Failed to record frame: Error creating EGLImage - "
+ << FormatEGLError(EglGetError());
+ return false;
+ }
+
+ // create GL 2D texture for framebuffer
+ if (!texture_) {
+ GlGenTextures(1, &texture_);
+ GlTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ GlTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ GlTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ GlTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ }
+ GlBindTexture(GL_TEXTURE_2D, texture_);
+ GlEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image);
+
+ if (!fbo_) {
+ GlGenFramebuffers(1, &fbo_);
+ }
+
+ GlBindFramebuffer(GL_FRAMEBUFFER, fbo_);
+ GlFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+ texture_, 0);
+ if (GlCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
+ RTC_LOG(LS_ERROR) << "Failed to bind DMA buf framebuffer";
+ EglDestroyImageKHR(egl_.display, image);
+ return false;
+ }
+
+ GLenum gl_format = GL_BGRA;
+ switch (format) {
+ case SPA_VIDEO_FORMAT_RGBx:
+ gl_format = GL_RGBA;
+ break;
+ case SPA_VIDEO_FORMAT_RGBA:
+ gl_format = GL_RGBA;
+ break;
+ case SPA_VIDEO_FORMAT_BGRx:
+ gl_format = GL_BGRA;
+ break;
+ default:
+ gl_format = GL_BGRA;
+ break;
+ }
+
+ GlReadPixels(offset.x(), offset.y(), buffer_size.width(),
+ buffer_size.height(), gl_format, GL_UNSIGNED_BYTE, data);
+
+ const GLenum error = GlGetError();
+ if (error) {
+ RTC_LOG(LS_ERROR) << "Failed to get image from DMA buffer.";
+ }
+
+ EglDestroyImageKHR(egl_.display, image);
+
+ return !error;
+}
+
+RTC_NO_SANITIZE("cfi-icall")
+std::vector<uint64_t> EglDmaBuf::QueryDmaBufModifiers(uint32_t format) {
+ if (!egl_initialized_) {
+ return {};
+ }
+
+ // Explicit modifiers not supported, return just DRM_FORMAT_MOD_INVALID as we
+ // can still use modifier-less DMA-BUFs if we have required extension
+ if (EglQueryDmaBufFormatsEXT == nullptr ||
+ EglQueryDmaBufModifiersEXT == nullptr) {
+ return has_image_dma_buf_import_ext_
+ ? std::vector<uint64_t>{DRM_FORMAT_MOD_INVALID}
+ : std::vector<uint64_t>{};
+ }
+
+ uint32_t drm_format = SpaPixelFormatToDrmFormat(format);
+ // Should never happen as it's us who controls the list of supported formats
+ RTC_DCHECK(drm_format != DRM_FORMAT_INVALID);
+
+ EGLint count = 0;
+ EGLBoolean success =
+ EglQueryDmaBufFormatsEXT(egl_.display, 0, nullptr, &count);
+
+ if (!success || !count) {
+ RTC_LOG(LS_WARNING) << "Cannot query the number of formats.";
+ return {DRM_FORMAT_MOD_INVALID};
+ }
+
+ std::vector<uint32_t> formats(count);
+ if (!EglQueryDmaBufFormatsEXT(egl_.display, count,
+ reinterpret_cast<EGLint*>(formats.data()),
+ &count)) {
+ RTC_LOG(LS_WARNING) << "Cannot query a list of formats.";
+ return {DRM_FORMAT_MOD_INVALID};
+ }
+
+ if (std::find(formats.begin(), formats.end(), drm_format) == formats.end()) {
+ RTC_LOG(LS_WARNING) << "Format " << drm_format
+ << " not supported for modifiers.";
+ return {DRM_FORMAT_MOD_INVALID};
+ }
+
+ success = EglQueryDmaBufModifiersEXT(egl_.display, drm_format, 0, nullptr,
+ nullptr, &count);
+
+ if (!success || !count) {
+ RTC_LOG(LS_WARNING) << "Cannot query the number of modifiers.";
+ return {DRM_FORMAT_MOD_INVALID};
+ }
+
+ std::vector<uint64_t> modifiers(count);
+ if (!EglQueryDmaBufModifiersEXT(egl_.display, drm_format, count,
+ modifiers.data(), nullptr, &count)) {
+ RTC_LOG(LS_WARNING) << "Cannot query a list of modifiers.";
+ }
+
+ // Support modifier-less buffers
+ modifiers.push_back(DRM_FORMAT_MOD_INVALID);
+ return modifiers;
+}
+
+absl::optional<std::string> EglDmaBuf::GetRenderNode() {
+ int max_devices = drmGetDevices2(0, nullptr, 0);
+ if (max_devices <= 0) {
+ RTC_LOG(LS_ERROR) << "drmGetDevices2() has not found any devices (errno="
+ << -max_devices << ")";
+ return absl::nullopt;
+ }
+
+ std::vector<drmDevicePtr> devices(max_devices);
+ int ret = drmGetDevices2(0, devices.data(), max_devices);
+ if (ret < 0) {
+ RTC_LOG(LS_ERROR) << "drmGetDevices2() returned an error " << ret;
+ return absl::nullopt;
+ }
+
+ std::string render_node;
+
+ for (const drmDevicePtr& device : devices) {
+ if (device->available_nodes & (1 << DRM_NODE_RENDER)) {
+ render_node = device->nodes[DRM_NODE_RENDER];
+ break;
+ }
+ }
+
+ drmFreeDevices(devices.data(), ret);
+ return render_node;
+}
+
+} // namespace webrtc
diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/wayland/egl_dmabuf.h b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/egl_dmabuf.h
new file mode 100644
index 0000000000..22a8f5ab52
--- /dev/null
+++ b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/egl_dmabuf.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2021 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_EGL_DMABUF_H_
+#define MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_EGL_DMABUF_H_
+
+#include <epoxy/egl.h>
+#include <epoxy/gl.h>
+#include <gbm.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "absl/types/optional.h"
+#include "modules/desktop_capture/desktop_geometry.h"
+
+namespace webrtc {
+
+class EglDmaBuf {
+ public:
+ struct EGLStruct {
+ std::vector<std::string> extensions;
+ EGLDisplay display = EGL_NO_DISPLAY;
+ EGLContext context = EGL_NO_CONTEXT;
+ };
+
+ struct PlaneData {
+ int32_t fd;
+ uint32_t stride;
+ uint32_t offset;
+ };
+
+ EglDmaBuf();
+ ~EglDmaBuf();
+
+ // Returns whether the image was successfully imported from
+ // given DmaBuf and its parameters
+ bool ImageFromDmaBuf(const DesktopSize& size,
+ uint32_t format,
+ const std::vector<PlaneData>& plane_datas,
+ uint64_t modifiers,
+ const DesktopVector& offset,
+ const DesktopSize& buffer_size,
+ uint8_t* data);
+ std::vector<uint64_t> QueryDmaBufModifiers(uint32_t format);
+
+ bool IsEglInitialized() const { return egl_initialized_; }
+
+ private:
+ bool GetClientExtensions(EGLDisplay dpy, EGLint name);
+
+ bool egl_initialized_ = false;
+ bool has_image_dma_buf_import_ext_ = false;
+ int32_t drm_fd_ = -1; // for GBM buffer mmap
+ gbm_device* gbm_device_ = nullptr; // for passed GBM buffer retrieval
+
+ GLuint fbo_ = 0;
+ GLuint texture_ = 0;
+ EGLStruct egl_;
+
+ absl::optional<std::string> GetRenderNode();
+};
+
+} // namespace webrtc
+
+#endif // MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_EGL_DMABUF_H_
diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/wayland/mouse_cursor_monitor_pipewire.cc b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/mouse_cursor_monitor_pipewire.cc
new file mode 100644
index 0000000000..3d33b0fbb8
--- /dev/null
+++ b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/mouse_cursor_monitor_pipewire.cc
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2022 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "modules/desktop_capture/linux/wayland/mouse_cursor_monitor_pipewire.h"
+
+#include <utility>
+
+#include "modules/desktop_capture/desktop_capture_options.h"
+#include "modules/desktop_capture/desktop_capturer.h"
+#include "rtc_base/checks.h"
+#include "rtc_base/logging.h"
+
+namespace webrtc {
+
+MouseCursorMonitorPipeWire::MouseCursorMonitorPipeWire(
+ const DesktopCaptureOptions& options)
+ : options_(options) {
+ sequence_checker_.Detach();
+}
+
+MouseCursorMonitorPipeWire::~MouseCursorMonitorPipeWire() {}
+
+void MouseCursorMonitorPipeWire::Init(Callback* callback, Mode mode) {
+ RTC_DCHECK_RUN_ON(&sequence_checker_);
+ RTC_DCHECK(!callback_);
+ RTC_DCHECK(callback);
+
+ callback_ = callback;
+ mode_ = mode;
+}
+
+void MouseCursorMonitorPipeWire::Capture() {
+ RTC_DCHECK_RUN_ON(&sequence_checker_);
+ RTC_DCHECK(callback_);
+
+ std::unique_ptr<MouseCursor> mouse_cursor =
+ options_.screencast_stream()->CaptureCursor();
+
+ if (mouse_cursor && mouse_cursor->image()->data()) {
+ callback_->OnMouseCursor(mouse_cursor.release());
+ }
+
+ if (mode_ == SHAPE_AND_POSITION) {
+ absl::optional<DesktopVector> mouse_cursor_position =
+ options_.screencast_stream()->CaptureCursorPosition();
+ if (mouse_cursor_position) {
+ callback_->OnMouseCursorPosition(mouse_cursor_position.value());
+ }
+ }
+}
+
+} // namespace webrtc
diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/wayland/mouse_cursor_monitor_pipewire.h b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/mouse_cursor_monitor_pipewire.h
new file mode 100644
index 0000000000..da670bece9
--- /dev/null
+++ b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/mouse_cursor_monitor_pipewire.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2022 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_MOUSE_CURSOR_MONITOR_PIPEWIRE_H_
+#define MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_MOUSE_CURSOR_MONITOR_PIPEWIRE_H_
+
+#include <memory>
+
+#include "api/scoped_refptr.h"
+#include "api/sequence_checker.h"
+#include "modules/desktop_capture/desktop_capture_options.h"
+#include "modules/desktop_capture/desktop_capture_types.h"
+#include "modules/desktop_capture/linux/wayland/shared_screencast_stream.h"
+#include "modules/desktop_capture/mouse_cursor.h"
+#include "modules/desktop_capture/mouse_cursor_monitor.h"
+#include "rtc_base/system/no_unique_address.h"
+
+namespace webrtc {
+
+class MouseCursorMonitorPipeWire : public MouseCursorMonitor {
+ public:
+ explicit MouseCursorMonitorPipeWire(const DesktopCaptureOptions& options);
+ ~MouseCursorMonitorPipeWire() override;
+
+ // MouseCursorMonitor:
+ void Init(Callback* callback, Mode mode) override;
+ void Capture() override;
+
+ DesktopCaptureOptions options_ RTC_GUARDED_BY(sequence_checker_);
+ Callback* callback_ RTC_GUARDED_BY(sequence_checker_) = nullptr;
+ Mode mode_ RTC_GUARDED_BY(sequence_checker_) = SHAPE_AND_POSITION;
+ RTC_NO_UNIQUE_ADDRESS SequenceChecker sequence_checker_;
+};
+
+} // namespace webrtc
+
+#endif // MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_MOUSE_CURSOR_MONITOR_PIPEWIRE_H_
diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/wayland/portal_request_response.h b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/portal_request_response.h
new file mode 100644
index 0000000000..2589479347
--- /dev/null
+++ b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/portal_request_response.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) 2022 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_PORTAL_REQUEST_RESPONSE_H_
+#define MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_PORTAL_REQUEST_RESPONSE_H_
+
+// TODO(bugs.webrtc.org/14187): remove when all users are gone
+#include "modules/portal/portal_request_response.h"
+
+#endif // MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_PORTAL_REQUEST_RESPONSE_H_
diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/wayland/restore_token_manager.cc b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/restore_token_manager.cc
new file mode 100644
index 0000000000..5ca9b957a9
--- /dev/null
+++ b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/restore_token_manager.cc
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2022 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "modules/desktop_capture/linux/wayland/restore_token_manager.h"
+
+namespace webrtc {
+
+// static
+RestoreTokenManager& RestoreTokenManager::GetInstance() {
+ static webrtc::RestoreTokenManager* manager = new RestoreTokenManager();
+ return *manager;
+}
+
+void RestoreTokenManager::AddToken(DesktopCapturer::SourceId id,
+ const std::string& token) {
+ restore_tokens_.insert({id, token});
+}
+
+std::string RestoreTokenManager::TakeToken(DesktopCapturer::SourceId id) {
+ std::string token = restore_tokens_[id];
+ // Remove the token as it cannot be used anymore
+ restore_tokens_.erase(id);
+ return token;
+}
+
+DesktopCapturer::SourceId RestoreTokenManager::GetUnusedId() {
+ return ++last_source_id_;
+}
+
+} // namespace webrtc
diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/wayland/restore_token_manager.h b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/restore_token_manager.h
new file mode 100644
index 0000000000..174bef121f
--- /dev/null
+++ b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/restore_token_manager.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2022 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_RESTORE_TOKEN_MANAGER_H_
+#define MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_RESTORE_TOKEN_MANAGER_H_
+
+#include <mutex>
+#include <string>
+#include <unordered_map>
+
+#include "modules/desktop_capture/desktop_capturer.h"
+
+namespace webrtc {
+
+class RestoreTokenManager {
+ public:
+ RestoreTokenManager(const RestoreTokenManager& manager) = delete;
+ RestoreTokenManager& operator=(const RestoreTokenManager& manager) = delete;
+
+ static RestoreTokenManager& GetInstance();
+
+ void AddToken(DesktopCapturer::SourceId id, const std::string& token);
+ std::string TakeToken(DesktopCapturer::SourceId id);
+
+ // Returns a source ID which does not have any token associated with it yet.
+ DesktopCapturer::SourceId GetUnusedId();
+
+ private:
+ RestoreTokenManager() = default;
+ ~RestoreTokenManager() = default;
+
+ DesktopCapturer::SourceId last_source_id_ = 0;
+
+ std::unordered_map<DesktopCapturer::SourceId, std::string> restore_tokens_;
+};
+
+} // namespace webrtc
+
+#endif // MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_RESTORE_TOKEN_MANAGER_H_
diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/wayland/scoped_glib.h b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/scoped_glib.h
new file mode 100644
index 0000000000..1361f84328
--- /dev/null
+++ b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/scoped_glib.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2022 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_SCOPED_GLIB_H_
+#define MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_SCOPED_GLIB_H_
+
+// TODO(bugs.webrtc.org/14187): remove when all users are gone
+#include "modules/portal/scoped_glib.h"
+
+#endif // MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_SCOPED_GLIB_H_
diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/wayland/screen_capture_portal_interface.cc b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/screen_capture_portal_interface.cc
new file mode 100644
index 0000000000..1c7cc379df
--- /dev/null
+++ b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/screen_capture_portal_interface.cc
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2022 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+#include "modules/desktop_capture/linux/wayland/screen_capture_portal_interface.h"
+
+#include <string>
+
+#include "modules/portal/xdg_desktop_portal_utils.h"
+#include "rtc_base/logging.h"
+
+namespace webrtc {
+namespace xdg_portal {
+
+void ScreenCapturePortalInterface::RequestSessionUsingProxy(
+ GAsyncResult* result) {
+ Scoped<GError> error;
+ GDBusProxy* proxy = g_dbus_proxy_new_finish(result, error.receive());
+ if (!proxy) {
+ // Ignore the error caused by user cancelling the request via `cancellable_`
+ if (g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ return;
+ RTC_LOG(LS_ERROR) << "Failed to get a proxy for the portal: "
+ << error->message;
+ OnPortalDone(RequestResponse::kError);
+ return;
+ }
+
+ RTC_LOG(LS_INFO) << "Successfully created proxy for the portal.";
+ RequestSession(proxy);
+}
+
+void ScreenCapturePortalInterface::OnSessionRequestResult(
+ GDBusProxy* proxy,
+ GAsyncResult* result) {
+ Scoped<GError> error;
+ Scoped<GVariant> variant(
+ g_dbus_proxy_call_finish(proxy, result, error.receive()));
+ if (!variant) {
+ // Ignore the error caused by user cancelling the request via `cancellable_`
+ if (g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ return;
+ RTC_LOG(LS_ERROR) << "Failed to request session: " << error->message;
+ OnPortalDone(RequestResponse::kError);
+ return;
+ }
+
+ RTC_LOG(LS_INFO) << "Initializing the session.";
+
+ Scoped<char> handle;
+ g_variant_get_child(variant.get(), /*index=*/0, /*format_string=*/"o",
+ &handle);
+ if (!handle) {
+ RTC_LOG(LS_ERROR) << "Failed to initialize the session.";
+ OnPortalDone(RequestResponse::kError);
+ return;
+ }
+}
+
+void ScreenCapturePortalInterface::RegisterSessionClosedSignalHandler(
+ const SessionClosedSignalHandler session_close_signal_handler,
+ GVariant* parameters,
+ GDBusConnection* connection,
+ std::string& session_handle,
+ guint& session_closed_signal_id) {
+ uint32_t portal_response = 2;
+ Scoped<GVariant> response_data;
+ g_variant_get(parameters, /*format_string=*/"(u@a{sv})", &portal_response,
+ response_data.receive());
+
+ if (RequestResponseFromPortalResponse(portal_response) !=
+ RequestResponse::kSuccess) {
+ RTC_LOG(LS_ERROR) << "Failed to request the session subscription.";
+ OnPortalDone(RequestResponse::kError);
+ return;
+ }
+
+ Scoped<GVariant> g_session_handle(
+ g_variant_lookup_value(response_data.get(), /*key=*/"session_handle",
+ /*expected_type=*/nullptr));
+ session_handle = g_variant_get_string(
+ /*value=*/g_session_handle.get(), /*length=*/nullptr);
+
+ if (session_handle.empty()) {
+ RTC_LOG(LS_ERROR) << "Could not get session handle despite valid response";
+ OnPortalDone(RequestResponse::kError);
+ return;
+ }
+
+ session_closed_signal_id = g_dbus_connection_signal_subscribe(
+ connection, kDesktopBusName, kSessionInterfaceName, /*member=*/"Closed",
+ session_handle.c_str(), /*arg0=*/nullptr, G_DBUS_SIGNAL_FLAGS_NONE,
+ session_close_signal_handler, this, /*user_data_free_func=*/nullptr);
+}
+
+void ScreenCapturePortalInterface::OnStartRequestResult(GDBusProxy* proxy,
+ GAsyncResult* result) {
+ Scoped<GError> error;
+ Scoped<GVariant> variant(
+ g_dbus_proxy_call_finish(proxy, result, error.receive()));
+ if (!variant) {
+ if (g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ return;
+ RTC_LOG(LS_ERROR) << "Failed to start the portal session: "
+ << error->message;
+ OnPortalDone(RequestResponse::kError);
+ return;
+ }
+
+ Scoped<char> handle;
+ g_variant_get_child(variant.get(), 0, "o", handle.receive());
+ if (!handle) {
+ RTC_LOG(LS_ERROR) << "Failed to initialize the start portal session.";
+ OnPortalDone(RequestResponse::kError);
+ return;
+ }
+
+ RTC_LOG(LS_INFO) << "Subscribed to the start signal.";
+}
+
+} // namespace xdg_portal
+} // namespace webrtc
diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/wayland/screen_capture_portal_interface.h b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/screen_capture_portal_interface.h
new file mode 100644
index 0000000000..7953c6470c
--- /dev/null
+++ b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/screen_capture_portal_interface.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2022 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_SCREEN_CAPTURE_PORTAL_INTERFACE_H_
+#define MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_SCREEN_CAPTURE_PORTAL_INTERFACE_H_
+
+#include <gio/gio.h>
+
+#include <string>
+
+#include "modules/portal/portal_request_response.h"
+#include "modules/portal/scoped_glib.h"
+#include "modules/portal/xdg_desktop_portal_utils.h"
+#include "modules/portal/xdg_session_details.h"
+
+namespace webrtc {
+namespace xdg_portal {
+
+using SessionClosedSignalHandler = void (*)(GDBusConnection*,
+ const char*,
+ const char*,
+ const char*,
+ const char*,
+ GVariant*,
+ gpointer);
+
+// A base class for XDG desktop portals that can capture desktop/screen.
+// Note: downstream clients inherit from this class so it is advisable to
+// provide a default implementation of any new virtual methods that may be added
+// to this class.
+class RTC_EXPORT ScreenCapturePortalInterface {
+ public:
+ virtual ~ScreenCapturePortalInterface() {}
+ // Gets details about the session such as session handle.
+ virtual xdg_portal::SessionDetails GetSessionDetails() { return {}; }
+ // Starts the portal setup.
+ virtual void Start() {}
+
+ // Stops and cleans up the portal.
+ virtual void Stop() {}
+
+ // Notifies observers about the success/fail state of the portal
+ // request/response.
+ virtual void OnPortalDone(xdg_portal::RequestResponse result) {}
+ // Sends a create session request to the portal.
+ virtual void RequestSession(GDBusProxy* proxy) {}
+
+ // Following methods should not be made virtual as they share a common
+ // implementation between portals.
+
+ // Requests portal session using the proxy object.
+ void RequestSessionUsingProxy(GAsyncResult* result);
+ // Handles the session request result.
+ void OnSessionRequestResult(GDBusProxy* proxy, GAsyncResult* result);
+ // Subscribes to session close signal and sets up a handler for it.
+ void RegisterSessionClosedSignalHandler(
+ const SessionClosedSignalHandler session_close_signal_handler,
+ GVariant* parameters,
+ GDBusConnection* connection,
+ std::string& session_handle,
+ guint& session_closed_signal_id);
+ // Handles the result of session start request.
+ void OnStartRequestResult(GDBusProxy* proxy, GAsyncResult* result);
+};
+
+} // namespace xdg_portal
+} // namespace webrtc
+
+#endif // MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_SCREEN_CAPTURE_PORTAL_INTERFACE_H_
diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/wayland/screencast_portal.cc b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/screencast_portal.cc
new file mode 100644
index 0000000000..e7aaee001b
--- /dev/null
+++ b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/screencast_portal.cc
@@ -0,0 +1,471 @@
+/*
+ * Copyright 2022 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "modules/desktop_capture/linux/wayland/screencast_portal.h"
+
+#include <gio/gunixfdlist.h>
+#include <glib-object.h>
+
+#include "modules/portal/scoped_glib.h"
+#include "modules/portal/xdg_desktop_portal_utils.h"
+#include "rtc_base/checks.h"
+#include "rtc_base/logging.h"
+
+namespace webrtc {
+namespace {
+
+using xdg_portal::kScreenCastInterfaceName;
+using xdg_portal::PrepareSignalHandle;
+using xdg_portal::RequestResponse;
+using xdg_portal::RequestSessionProxy;
+using xdg_portal::SetupRequestResponseSignal;
+using xdg_portal::SetupSessionRequestHandlers;
+using xdg_portal::StartSessionRequest;
+using xdg_portal::TearDownSession;
+using xdg_portal::RequestResponseFromPortalResponse;
+
+} // namespace
+
+// static
+ScreenCastPortal::CaptureSourceType ScreenCastPortal::ToCaptureSourceType(
+ CaptureType type) {
+ switch (type) {
+ case CaptureType::kScreen:
+ return ScreenCastPortal::CaptureSourceType::kScreen;
+ case CaptureType::kWindow:
+ return ScreenCastPortal::CaptureSourceType::kWindow;
+ case CaptureType::kAnyScreenContent:
+ return ScreenCastPortal::CaptureSourceType::kAnyScreenContent;
+ }
+}
+
+ScreenCastPortal::ScreenCastPortal(CaptureType type, PortalNotifier* notifier)
+ : ScreenCastPortal(type,
+ notifier,
+ OnProxyRequested,
+ OnSourcesRequestResponseSignal,
+ this) {}
+
+ScreenCastPortal::ScreenCastPortal(
+ CaptureType type,
+ PortalNotifier* notifier,
+ ProxyRequestResponseHandler proxy_request_response_handler,
+ SourcesRequestResponseSignalHandler sources_request_response_signal_handler,
+ gpointer user_data,
+ bool prefer_cursor_embedded)
+ : notifier_(notifier),
+ capture_source_type_(ToCaptureSourceType(type)),
+ cursor_mode_(prefer_cursor_embedded ? CursorMode::kEmbedded
+ : CursorMode::kMetadata),
+ proxy_request_response_handler_(proxy_request_response_handler),
+ sources_request_response_signal_handler_(
+ sources_request_response_signal_handler),
+ user_data_(user_data) {}
+
+ScreenCastPortal::~ScreenCastPortal() {
+ Stop();
+}
+
+void ScreenCastPortal::Stop() {
+ UnsubscribeSignalHandlers();
+ TearDownSession(std::move(session_handle_), proxy_, cancellable_,
+ connection_);
+ session_handle_ = "";
+ cancellable_ = nullptr;
+ proxy_ = nullptr;
+ restore_token_ = "";
+
+ if (pw_fd_ != -1) {
+ close(pw_fd_);
+ pw_fd_ = -1;
+ }
+}
+
+void ScreenCastPortal::UnsubscribeSignalHandlers() {
+ if (start_request_signal_id_) {
+ g_dbus_connection_signal_unsubscribe(connection_, start_request_signal_id_);
+ start_request_signal_id_ = 0;
+ }
+
+ if (sources_request_signal_id_) {
+ g_dbus_connection_signal_unsubscribe(connection_,
+ sources_request_signal_id_);
+ sources_request_signal_id_ = 0;
+ }
+
+ if (session_request_signal_id_) {
+ g_dbus_connection_signal_unsubscribe(connection_,
+ session_request_signal_id_);
+ session_request_signal_id_ = 0;
+ }
+}
+
+void ScreenCastPortal::SetSessionDetails(
+ const xdg_portal::SessionDetails& session_details) {
+ if (session_details.proxy) {
+ proxy_ = session_details.proxy;
+ connection_ = g_dbus_proxy_get_connection(proxy_);
+ }
+ if (session_details.cancellable) {
+ cancellable_ = session_details.cancellable;
+ }
+ if (!session_details.session_handle.empty()) {
+ session_handle_ = session_details.session_handle;
+ }
+ if (session_details.pipewire_stream_node_id) {
+ pw_stream_node_id_ = session_details.pipewire_stream_node_id;
+ }
+}
+
+void ScreenCastPortal::Start() {
+ cancellable_ = g_cancellable_new();
+ RequestSessionProxy(kScreenCastInterfaceName, proxy_request_response_handler_,
+ cancellable_, this);
+}
+
+xdg_portal::SessionDetails ScreenCastPortal::GetSessionDetails() {
+ return {}; // No-op
+}
+
+void ScreenCastPortal::OnPortalDone(RequestResponse result) {
+ notifier_->OnScreenCastRequestResult(result, pw_stream_node_id_, pw_fd_);
+ if (result != RequestResponse::kSuccess) {
+ Stop();
+ }
+}
+
+// static
+void ScreenCastPortal::OnProxyRequested(GObject* gobject,
+ GAsyncResult* result,
+ gpointer user_data) {
+ static_cast<ScreenCastPortal*>(user_data)->RequestSessionUsingProxy(result);
+}
+
+void ScreenCastPortal::RequestSession(GDBusProxy* proxy) {
+ proxy_ = proxy;
+ connection_ = g_dbus_proxy_get_connection(proxy_);
+ SetupSessionRequestHandlers(
+ "webrtc", OnSessionRequested, OnSessionRequestResponseSignal, connection_,
+ proxy_, cancellable_, portal_handle_, session_request_signal_id_, this);
+}
+
+// static
+void ScreenCastPortal::OnSessionRequested(GDBusProxy* proxy,
+ GAsyncResult* result,
+ gpointer user_data) {
+ static_cast<ScreenCastPortal*>(user_data)->OnSessionRequestResult(proxy,
+ result);
+}
+
+// static
+void ScreenCastPortal::OnSessionRequestResponseSignal(
+ GDBusConnection* connection,
+ const char* sender_name,
+ const char* object_path,
+ const char* interface_name,
+ const char* signal_name,
+ GVariant* parameters,
+ gpointer user_data) {
+ ScreenCastPortal* that = static_cast<ScreenCastPortal*>(user_data);
+ RTC_DCHECK(that);
+ that->RegisterSessionClosedSignalHandler(
+ OnSessionClosedSignal, parameters, that->connection_,
+ that->session_handle_, that->session_closed_signal_id_);
+
+ // Do not continue if we don't get session_handle back. The call above will
+ // already notify the capturer there is a failure, but we would still continue
+ // to make following request and crash on that.
+ if (!that->session_handle_.empty()) {
+ that->SourcesRequest();
+ }
+}
+
+// static
+void ScreenCastPortal::OnSessionClosedSignal(GDBusConnection* connection,
+ const char* sender_name,
+ const char* object_path,
+ const char* interface_name,
+ const char* signal_name,
+ GVariant* parameters,
+ gpointer user_data) {
+ ScreenCastPortal* that = static_cast<ScreenCastPortal*>(user_data);
+ RTC_DCHECK(that);
+
+ RTC_LOG(LS_INFO) << "Received closed signal from session.";
+
+ that->notifier_->OnScreenCastSessionClosed();
+
+ // Unsubscribe from the signal and free the session handle to avoid calling
+ // Session::Close from the destructor since it's already closed
+ g_dbus_connection_signal_unsubscribe(that->connection_,
+ that->session_closed_signal_id_);
+}
+
+void ScreenCastPortal::SourcesRequest() {
+ GVariantBuilder builder;
+ Scoped<char> variant_string;
+
+ g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
+ // We want to record monitor content.
+ g_variant_builder_add(
+ &builder, "{sv}", "types",
+ g_variant_new_uint32(static_cast<uint32_t>(capture_source_type_)));
+ // We don't want to allow selection of multiple sources.
+ g_variant_builder_add(&builder, "{sv}", "multiple",
+ g_variant_new_boolean(false));
+
+ Scoped<GVariant> cursorModesVariant(
+ g_dbus_proxy_get_cached_property(proxy_, "AvailableCursorModes"));
+ if (cursorModesVariant.get()) {
+ uint32_t modes = 0;
+ g_variant_get(cursorModesVariant.get(), "u", &modes);
+ // Make request only if this mode is advertised by the portal
+ // implementation.
+ if (modes & static_cast<uint32_t>(cursor_mode_)) {
+ g_variant_builder_add(
+ &builder, "{sv}", "cursor_mode",
+ g_variant_new_uint32(static_cast<uint32_t>(cursor_mode_)));
+ }
+ }
+
+ Scoped<GVariant> versionVariant(
+ g_dbus_proxy_get_cached_property(proxy_, "version"));
+ if (versionVariant.get()) {
+ uint32_t version = 0;
+ g_variant_get(versionVariant.get(), "u", &version);
+ // Make request only if xdg-desktop-portal has required API version
+ if (version >= 4) {
+ g_variant_builder_add(
+ &builder, "{sv}", "persist_mode",
+ g_variant_new_uint32(static_cast<uint32_t>(persist_mode_)));
+ if (!restore_token_.empty()) {
+ g_variant_builder_add(&builder, "{sv}", "restore_token",
+ g_variant_new_string(restore_token_.c_str()));
+ }
+ }
+ }
+
+ variant_string = g_strdup_printf("webrtc%d", g_random_int_range(0, G_MAXINT));
+ g_variant_builder_add(&builder, "{sv}", "handle_token",
+ g_variant_new_string(variant_string.get()));
+
+ sources_handle_ = PrepareSignalHandle(variant_string.get(), connection_);
+ sources_request_signal_id_ = SetupRequestResponseSignal(
+ sources_handle_.c_str(), sources_request_response_signal_handler_,
+ user_data_, connection_);
+
+ RTC_LOG(LS_INFO) << "Requesting sources from the screen cast session.";
+ g_dbus_proxy_call(
+ proxy_, "SelectSources",
+ g_variant_new("(oa{sv})", session_handle_.c_str(), &builder),
+ G_DBUS_CALL_FLAGS_NONE, /*timeout=*/-1, cancellable_,
+ reinterpret_cast<GAsyncReadyCallback>(OnSourcesRequested), this);
+}
+
+// static
+void ScreenCastPortal::OnSourcesRequested(GDBusProxy* proxy,
+ GAsyncResult* result,
+ gpointer user_data) {
+ ScreenCastPortal* that = static_cast<ScreenCastPortal*>(user_data);
+ RTC_DCHECK(that);
+
+ Scoped<GError> error;
+ Scoped<GVariant> variant(
+ g_dbus_proxy_call_finish(proxy, result, error.receive()));
+ if (!variant) {
+ if (g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ return;
+ RTC_LOG(LS_ERROR) << "Failed to request the sources: " << error->message;
+ that->OnPortalDone(RequestResponse::kError);
+ return;
+ }
+
+ RTC_LOG(LS_INFO) << "Sources requested from the screen cast session.";
+
+ Scoped<char> handle;
+ g_variant_get_child(variant.get(), 0, "o", handle.receive());
+ if (!handle) {
+ RTC_LOG(LS_ERROR) << "Failed to initialize the screen cast session.";
+ if (that->sources_request_signal_id_) {
+ g_dbus_connection_signal_unsubscribe(that->connection_,
+ that->sources_request_signal_id_);
+ that->sources_request_signal_id_ = 0;
+ }
+ that->OnPortalDone(RequestResponse::kError);
+ return;
+ }
+
+ RTC_LOG(LS_INFO) << "Subscribed to sources signal.";
+}
+
+// static
+void ScreenCastPortal::OnSourcesRequestResponseSignal(
+ GDBusConnection* connection,
+ const char* sender_name,
+ const char* object_path,
+ const char* interface_name,
+ const char* signal_name,
+ GVariant* parameters,
+ gpointer user_data) {
+ ScreenCastPortal* that = static_cast<ScreenCastPortal*>(user_data);
+ RTC_DCHECK(that);
+
+ RTC_LOG(LS_INFO) << "Received sources signal from session.";
+
+ uint32_t portal_response;
+ g_variant_get(parameters, "(u@a{sv})", &portal_response, nullptr);
+ if (portal_response) {
+ RTC_LOG(LS_ERROR)
+ << "Failed to select sources for the screen cast session.";
+ that->OnPortalDone(RequestResponse::kError);
+ return;
+ }
+
+ that->StartRequest();
+}
+
+void ScreenCastPortal::StartRequest() {
+ StartSessionRequest("webrtc", session_handle_, OnStartRequestResponseSignal,
+ OnStartRequested, proxy_, connection_, cancellable_,
+ start_request_signal_id_, start_handle_, this);
+}
+
+// static
+void ScreenCastPortal::OnStartRequested(GDBusProxy* proxy,
+ GAsyncResult* result,
+ gpointer user_data) {
+ static_cast<ScreenCastPortal*>(user_data)->OnStartRequestResult(proxy,
+ result);
+}
+
+// static
+void ScreenCastPortal::OnStartRequestResponseSignal(GDBusConnection* connection,
+ const char* sender_name,
+ const char* object_path,
+ const char* interface_name,
+ const char* signal_name,
+ GVariant* parameters,
+ gpointer user_data) {
+ ScreenCastPortal* that = static_cast<ScreenCastPortal*>(user_data);
+ RTC_DCHECK(that);
+
+ RTC_LOG(LS_INFO) << "Start signal received.";
+ uint32_t portal_response;
+ Scoped<GVariant> response_data;
+ Scoped<GVariantIter> iter;
+ Scoped<char> restore_token;
+ g_variant_get(parameters, "(u@a{sv})", &portal_response,
+ response_data.receive());
+ if (portal_response || !response_data) {
+ RTC_LOG(LS_ERROR) << "Failed to start the screen cast session.";
+ that->OnPortalDone(RequestResponseFromPortalResponse(portal_response));
+ return;
+ }
+
+ // Array of PipeWire streams. See
+ // https://github.com/flatpak/xdg-desktop-portal/blob/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())) {
+ uint32_t stream_id;
+ uint32_t type;
+ Scoped<GVariant> options;
+
+ g_variant_get(variant.get(), "(u@a{sv})", &stream_id, options.receive());
+ RTC_DCHECK(options.get());
+
+ if (g_variant_lookup(options.get(), "source_type", "u", &type)) {
+ that->capture_source_type_ =
+ static_cast<ScreenCastPortal::CaptureSourceType>(type);
+ }
+
+ that->pw_stream_node_id_ = stream_id;
+
+ break;
+ }
+ }
+
+ if (g_variant_lookup(response_data.get(), "restore_token", "s",
+ restore_token.receive())) {
+ that->restore_token_ = restore_token.get();
+ }
+
+ that->OpenPipeWireRemote();
+}
+
+uint32_t ScreenCastPortal::pipewire_stream_node_id() {
+ return pw_stream_node_id_;
+}
+
+void ScreenCastPortal::SetPersistMode(ScreenCastPortal::PersistMode mode) {
+ persist_mode_ = mode;
+}
+
+void ScreenCastPortal::SetRestoreToken(const std::string& token) {
+ restore_token_ = token;
+}
+
+std::string ScreenCastPortal::RestoreToken() const {
+ return restore_token_;
+}
+
+void ScreenCastPortal::OpenPipeWireRemote() {
+ GVariantBuilder builder;
+ g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
+
+ RTC_LOG(LS_INFO) << "Opening the PipeWire remote.";
+
+ g_dbus_proxy_call_with_unix_fd_list(
+ proxy_, "OpenPipeWireRemote",
+ g_variant_new("(oa{sv})", session_handle_.c_str(), &builder),
+ G_DBUS_CALL_FLAGS_NONE, /*timeout=*/-1, /*fd_list=*/nullptr, cancellable_,
+ reinterpret_cast<GAsyncReadyCallback>(OnOpenPipeWireRemoteRequested),
+ this);
+}
+
+// static
+void ScreenCastPortal::OnOpenPipeWireRemoteRequested(GDBusProxy* proxy,
+ GAsyncResult* result,
+ gpointer user_data) {
+ ScreenCastPortal* that = static_cast<ScreenCastPortal*>(user_data);
+ RTC_DCHECK(that);
+
+ Scoped<GError> error;
+ Scoped<GUnixFDList> outlist;
+ Scoped<GVariant> variant(g_dbus_proxy_call_with_unix_fd_list_finish(
+ proxy, outlist.receive(), result, error.receive()));
+ if (!variant) {
+ if (g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ return;
+ RTC_LOG(LS_ERROR) << "Failed to open the PipeWire remote: "
+ << error->message;
+ that->OnPortalDone(RequestResponse::kError);
+ return;
+ }
+
+ int32_t index;
+ g_variant_get(variant.get(), "(h)", &index);
+
+ that->pw_fd_ = g_unix_fd_list_get(outlist.get(), index, error.receive());
+
+ if (that->pw_fd_ == -1) {
+ RTC_LOG(LS_ERROR) << "Failed to get file descriptor from the list: "
+ << error->message;
+ that->OnPortalDone(RequestResponse::kError);
+ return;
+ }
+
+ that->OnPortalDone(RequestResponse::kSuccess);
+}
+
+} // namespace webrtc
diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/wayland/screencast_portal.h b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/screencast_portal.h
new file mode 100644
index 0000000000..37e55815e1
--- /dev/null
+++ b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/screencast_portal.h
@@ -0,0 +1,218 @@
+/*
+ * Copyright 2022 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_SCREENCAST_PORTAL_H_
+#define MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_SCREENCAST_PORTAL_H_
+
+#include <gio/gio.h>
+
+#include <string>
+
+#include "modules/desktop_capture/desktop_capture_types.h"
+#include "modules/desktop_capture/linux/wayland/screen_capture_portal_interface.h"
+#include "modules/portal/portal_request_response.h"
+#include "modules/portal/xdg_desktop_portal_utils.h"
+#include "modules/portal/xdg_session_details.h"
+
+namespace webrtc {
+
+class RTC_EXPORT ScreenCastPortal
+ : public xdg_portal::ScreenCapturePortalInterface {
+ public:
+ using ProxyRequestResponseHandler = void (*)(GObject* object,
+ GAsyncResult* result,
+ gpointer user_data);
+
+ using SourcesRequestResponseSignalHandler =
+ void (*)(GDBusConnection* connection,
+ const char* sender_name,
+ const char* object_path,
+ const char* interface_name,
+ const char* signal_name,
+ GVariant* parameters,
+ gpointer user_data);
+
+ // Values are set based on cursor mode property in
+ // xdg-desktop-portal/screencast
+ // https://github.com/flatpak/xdg-desktop-portal/blob/master/data/org.freedesktop.portal.ScreenCast.xml
+ enum class CursorMode : uint32_t {
+ // Mouse cursor will not be included in any form
+ kHidden = 0b01,
+ // Mouse cursor will be part of the screen content
+ kEmbedded = 0b10,
+ // Mouse cursor information will be send separately in form of metadata
+ kMetadata = 0b100
+ };
+
+ // Values are set based on persist mode property in
+ // xdg-desktop-portal/screencast
+ // https://github.com/flatpak/xdg-desktop-portal/blob/master/data/org.freedesktop.portal.ScreenCast.xml
+ enum class PersistMode : uint32_t {
+ // Do not allow to restore stream
+ kDoNotPersist = 0b00,
+ // The restore token is valid as long as the application is alive. It's
+ // stored in memory and revoked when the application closes its DBus
+ // connection
+ kTransient = 0b01,
+ // The restore token is stored in disk and is valid until the user manually
+ // revokes it
+ kPersistent = 0b10
+ };
+
+ // Interface that must be implemented by the ScreenCastPortal consumers.
+ class PortalNotifier {
+ public:
+ virtual void OnScreenCastRequestResult(xdg_portal::RequestResponse result,
+ uint32_t stream_node_id,
+ int fd) = 0;
+ virtual void OnScreenCastSessionClosed() = 0;
+
+ protected:
+ PortalNotifier() = default;
+ virtual ~PortalNotifier() = default;
+ };
+
+ ScreenCastPortal(CaptureType type, PortalNotifier* notifier);
+ ScreenCastPortal(CaptureType type,
+ PortalNotifier* notifier,
+ ProxyRequestResponseHandler proxy_request_response_handler,
+ SourcesRequestResponseSignalHandler
+ sources_request_response_signal_handler,
+ gpointer user_data,
+ // TODO(chromium:1291247): Remove the default option once
+ // downstream has been adjusted.
+ bool prefer_cursor_embedded = false);
+
+ ~ScreenCastPortal();
+
+ // Initialize ScreenCastPortal with series of DBus calls where we try to
+ // obtain all the required information, like PipeWire file descriptor and
+ // PipeWire stream node ID.
+ //
+ // The observer will return whether the communication with xdg-desktop-portal
+ // was successful and only then you will be able to get all the required
+ // information in order to continue working with PipeWire.
+ void Start() override;
+ void Stop() override;
+ xdg_portal::SessionDetails GetSessionDetails() override;
+
+ // Method to notify the reason for failure of a portal request.
+ void OnPortalDone(xdg_portal::RequestResponse result) override;
+
+ // Sends a create session request to the portal.
+ void RequestSession(GDBusProxy* proxy) override;
+
+ // Set of methods leveraged by remote desktop portal to setup a common session
+ // with screen cast portal.
+ void SetSessionDetails(const xdg_portal::SessionDetails& session_details);
+ uint32_t pipewire_stream_node_id();
+ void SourcesRequest();
+ void OpenPipeWireRemote();
+
+ // ScreenCast specific methods for stream restoration
+ void SetPersistMode(ScreenCastPortal::PersistMode mode);
+ void SetRestoreToken(const std::string& token);
+ std::string RestoreToken() const;
+
+ private:
+ // Values are set based on source type property in
+ // xdg-desktop-portal/screencast
+ // https://github.com/flatpak/xdg-desktop-portal/blob/master/data/org.freedesktop.portal.ScreenCast.xml
+ enum class CaptureSourceType : uint32_t {
+ kScreen = 0b01,
+ kWindow = 0b10,
+ kAnyScreenContent = kScreen | kWindow
+ };
+ static CaptureSourceType ToCaptureSourceType(CaptureType type);
+
+ PortalNotifier* notifier_;
+
+ // A PipeWire stream ID of stream we will be connecting to
+ uint32_t pw_stream_node_id_ = 0;
+ // A file descriptor of PipeWire socket
+ int pw_fd_ = -1;
+ // Restore token that can be used to restore previous session
+ std::string restore_token_;
+
+ CaptureSourceType capture_source_type_ =
+ ScreenCastPortal::CaptureSourceType::kScreen;
+
+ CursorMode cursor_mode_ = CursorMode::kMetadata;
+
+ PersistMode persist_mode_ = ScreenCastPortal::PersistMode::kDoNotPersist;
+
+ ProxyRequestResponseHandler proxy_request_response_handler_;
+ SourcesRequestResponseSignalHandler sources_request_response_signal_handler_;
+ gpointer user_data_;
+
+ GDBusConnection* connection_ = nullptr;
+ GDBusProxy* proxy_ = nullptr;
+ GCancellable* cancellable_ = nullptr;
+ std::string portal_handle_;
+ std::string session_handle_;
+ std::string sources_handle_;
+ std::string start_handle_;
+ guint session_request_signal_id_ = 0;
+ guint sources_request_signal_id_ = 0;
+ guint start_request_signal_id_ = 0;
+ guint session_closed_signal_id_ = 0;
+
+ void UnsubscribeSignalHandlers();
+ static void OnProxyRequested(GObject* object,
+ GAsyncResult* result,
+ gpointer user_data);
+ static void OnSessionRequested(GDBusProxy* proxy,
+ GAsyncResult* result,
+ gpointer user_data);
+ static void OnSessionRequestResponseSignal(GDBusConnection* connection,
+ const char* sender_name,
+ const char* object_path,
+ const char* interface_name,
+ const char* signal_name,
+ GVariant* parameters,
+ gpointer user_data);
+ static void OnSessionClosedSignal(GDBusConnection* connection,
+ const char* sender_name,
+ const char* object_path,
+ const char* interface_name,
+ const char* signal_name,
+ GVariant* parameters,
+ gpointer user_data);
+ static void OnSourcesRequested(GDBusProxy* proxy,
+ GAsyncResult* result,
+ gpointer user_data);
+ static void OnSourcesRequestResponseSignal(GDBusConnection* connection,
+ const char* sender_name,
+ const char* object_path,
+ const char* interface_name,
+ const char* 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 char* sender_name,
+ const char* object_path,
+ const char* interface_name,
+ const char* signal_name,
+ GVariant* parameters,
+ gpointer user_data);
+
+ static void OnOpenPipeWireRemoteRequested(GDBusProxy* proxy,
+ GAsyncResult* result,
+ gpointer user_data);
+};
+
+} // namespace webrtc
+
+#endif // MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_SCREENCAST_PORTAL_H_
diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/wayland/screencast_stream_utils.cc b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/screencast_stream_utils.cc
new file mode 100644
index 0000000000..0c4900d1cd
--- /dev/null
+++ b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/screencast_stream_utils.cc
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2022 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "modules/desktop_capture/linux/wayland/screencast_stream_utils.h"
+
+#include <libdrm/drm_fourcc.h>
+#include <pipewire/pipewire.h>
+#include <spa/param/video/format-utils.h>
+
+#include <string>
+
+#include "rtc_base/string_to_number.h"
+
+#if !PW_CHECK_VERSION(0, 3, 29)
+#define SPA_POD_PROP_FLAG_MANDATORY (1u << 3)
+#endif
+#if !PW_CHECK_VERSION(0, 3, 33)
+#define SPA_POD_PROP_FLAG_DONT_FIXATE (1u << 4)
+#endif
+
+namespace webrtc {
+
+PipeWireVersion PipeWireVersion::Parse(const absl::string_view& version) {
+ std::vector<absl::string_view> parsed_version = rtc::split(version, '.');
+
+ if (parsed_version.size() != 3) {
+ return {};
+ }
+
+ absl::optional<int> major = rtc::StringToNumber<int>(parsed_version.at(0));
+ absl::optional<int> minor = rtc::StringToNumber<int>(parsed_version.at(1));
+ absl::optional<int> micro = rtc::StringToNumber<int>(parsed_version.at(2));
+
+ // Return invalid version if we failed to parse it
+ if (!major || !minor || !micro) {
+ return {};
+ }
+
+ return {major.value(), minor.value(), micro.value()};
+}
+
+bool PipeWireVersion::operator>=(const PipeWireVersion& other) {
+ if (!major && !minor && !micro) {
+ return false;
+ }
+
+ return std::tie(major, minor, micro) >=
+ std::tie(other.major, other.minor, other.micro);
+}
+
+bool PipeWireVersion::operator<=(const PipeWireVersion& other) {
+ if (!major && !minor && !micro) {
+ return false;
+ }
+
+ return std::tie(major, minor, micro) <=
+ std::tie(other.major, other.minor, other.micro);
+}
+
+spa_pod* BuildFormat(spa_pod_builder* builder,
+ uint32_t format,
+ const std::vector<uint64_t>& modifiers,
+ const struct spa_rectangle* resolution) {
+ spa_pod_frame frames[2];
+ spa_rectangle pw_min_screen_bounds = spa_rectangle{1, 1};
+ spa_rectangle pw_max_screen_bounds = spa_rectangle{UINT32_MAX, UINT32_MAX};
+
+ spa_pod_builder_push_object(builder, &frames[0], SPA_TYPE_OBJECT_Format,
+ SPA_PARAM_EnumFormat);
+ spa_pod_builder_add(builder, SPA_FORMAT_mediaType,
+ SPA_POD_Id(SPA_MEDIA_TYPE_video), 0);
+ spa_pod_builder_add(builder, SPA_FORMAT_mediaSubtype,
+ SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), 0);
+ spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_format, SPA_POD_Id(format), 0);
+
+ if (modifiers.size()) {
+ if (modifiers.size() == 1 && modifiers[0] == DRM_FORMAT_MOD_INVALID) {
+ spa_pod_builder_prop(builder, SPA_FORMAT_VIDEO_modifier,
+ SPA_POD_PROP_FLAG_MANDATORY);
+ spa_pod_builder_long(builder, modifiers[0]);
+ } else {
+ spa_pod_builder_prop(
+ builder, SPA_FORMAT_VIDEO_modifier,
+ SPA_POD_PROP_FLAG_MANDATORY | SPA_POD_PROP_FLAG_DONT_FIXATE);
+ spa_pod_builder_push_choice(builder, &frames[1], SPA_CHOICE_Enum, 0);
+
+ // modifiers from the array
+ bool first = true;
+ for (int64_t val : modifiers) {
+ spa_pod_builder_long(builder, val);
+ // Add the first modifier twice as the very first value is the default
+ // option
+ if (first) {
+ spa_pod_builder_long(builder, val);
+ first = false;
+ }
+ }
+ spa_pod_builder_pop(builder, &frames[1]);
+ }
+ }
+
+ if (resolution) {
+ spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_size,
+ SPA_POD_Rectangle(resolution), 0);
+ } else {
+ spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_size,
+ SPA_POD_CHOICE_RANGE_Rectangle(&pw_min_screen_bounds,
+ &pw_min_screen_bounds,
+ &pw_max_screen_bounds),
+ 0);
+ }
+
+ return static_cast<spa_pod*>(spa_pod_builder_pop(builder, &frames[0]));
+}
+
+} // namespace webrtc
diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/wayland/screencast_stream_utils.h b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/screencast_stream_utils.h
new file mode 100644
index 0000000000..e04d7db931
--- /dev/null
+++ b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/screencast_stream_utils.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2022 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_SCREENCAST_STREAM_UTILS_H_
+#define MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_SCREENCAST_STREAM_UTILS_H_
+
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+#include "rtc_base/string_encode.h"
+
+struct spa_pod;
+struct spa_pod_builder;
+struct spa_rectangle;
+
+namespace webrtc {
+
+struct PipeWireVersion {
+ static PipeWireVersion Parse(const absl::string_view& version);
+
+ // Returns whether current version is newer or same as required version
+ bool operator>=(const PipeWireVersion& other);
+ // Returns whether current version is older or same as required version
+ bool operator<=(const PipeWireVersion& other);
+
+ int major = 0;
+ int minor = 0;
+ int micro = 0;
+};
+
+// Returns a spa_pod used to build PipeWire stream format using given
+// arguments. Modifiers are optional value and when present they will be
+// used with SPA_POD_PROP_FLAG_MANDATORY and SPA_POD_PROP_FLAG_DONT_FIXATE
+// flags.
+spa_pod* BuildFormat(spa_pod_builder* builder,
+ uint32_t format,
+ const std::vector<uint64_t>& modifiers,
+ const struct spa_rectangle* resolution);
+
+} // namespace webrtc
+
+#endif // MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_SCREENCAST_STREAM_UTILS_H_
diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/wayland/shared_screencast_stream.cc b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/shared_screencast_stream.cc
new file mode 100644
index 0000000000..49b6484510
--- /dev/null
+++ b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/shared_screencast_stream.cc
@@ -0,0 +1,965 @@
+/*
+ * Copyright 2022 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "modules/desktop_capture/linux/wayland/shared_screencast_stream.h"
+
+#include <fcntl.h>
+#include <libdrm/drm_fourcc.h>
+#include <pipewire/pipewire.h>
+#include <spa/param/video/format-utils.h>
+#include <sys/mman.h>
+
+#include <vector>
+
+#include "absl/memory/memory.h"
+#include "modules/desktop_capture/linux/wayland/egl_dmabuf.h"
+#include "modules/desktop_capture/linux/wayland/screencast_stream_utils.h"
+#include "modules/portal/pipewire_utils.h"
+#include "rtc_base/checks.h"
+#include "rtc_base/logging.h"
+#include "rtc_base/sanitizer.h"
+#include "rtc_base/synchronization/mutex.h"
+
+namespace webrtc {
+
+const int kBytesPerPixel = 4;
+const int kVideoDamageRegionCount = 16;
+
+constexpr int kCursorBpp = 4;
+constexpr int CursorMetaSize(int w, int h) {
+ return (sizeof(struct spa_meta_cursor) + sizeof(struct spa_meta_bitmap) +
+ w * h * kCursorBpp);
+}
+
+constexpr PipeWireVersion kDmaBufModifierMinVersion = {0, 3, 33};
+constexpr PipeWireVersion kDropSingleModifierMinVersion = {0, 3, 40};
+
+class ScopedBuf {
+ public:
+ ScopedBuf() {}
+ ScopedBuf(uint8_t* map, int map_size, int fd)
+ : map_(map), map_size_(map_size), fd_(fd) {}
+ ~ScopedBuf() {
+ if (map_ != MAP_FAILED) {
+ munmap(map_, map_size_);
+ }
+ }
+
+ explicit operator bool() { return map_ != MAP_FAILED; }
+
+ void initialize(uint8_t* map, int map_size, int fd) {
+ map_ = map;
+ map_size_ = map_size;
+ fd_ = fd;
+ }
+
+ uint8_t* get() { return map_; }
+
+ protected:
+ uint8_t* map_ = static_cast<uint8_t*>(MAP_FAILED);
+ int map_size_;
+ int fd_;
+};
+
+class SharedScreenCastStreamPrivate {
+ public:
+ SharedScreenCastStreamPrivate();
+ ~SharedScreenCastStreamPrivate();
+
+ bool StartScreenCastStream(uint32_t stream_node_id,
+ int fd,
+ uint32_t width = 0,
+ uint32_t height = 0,
+ bool is_cursor_embedded = false);
+ void UpdateScreenCastStreamResolution(uint32_t width, uint32_t height);
+ void SetUseDamageRegion(bool use_damage_region) {
+ use_damage_region_ = use_damage_region;
+ }
+ void SetObserver(SharedScreenCastStream::Observer* observer) {
+ observer_ = observer;
+ }
+ void StopScreenCastStream();
+ std::unique_ptr<SharedDesktopFrame> CaptureFrame();
+ std::unique_ptr<MouseCursor> CaptureCursor();
+ DesktopVector CaptureCursorPosition();
+
+ private:
+ // Stops the streams and cleans up any in-use elements.
+ void StopAndCleanupStream();
+
+ SharedScreenCastStream::Observer* observer_ = nullptr;
+
+ // Track damage region updates that were reported since the last time
+ // frame was captured
+ DesktopRegion damage_region_;
+
+ uint32_t pw_stream_node_id_ = 0;
+
+ DesktopSize stream_size_ = {};
+ DesktopSize frame_size_;
+
+ webrtc::Mutex queue_lock_;
+ ScreenCaptureFrameQueue<SharedDesktopFrame> queue_
+ RTC_GUARDED_BY(&queue_lock_);
+ std::unique_ptr<MouseCursor> mouse_cursor_;
+ DesktopVector mouse_cursor_position_ = DesktopVector(-1, -1);
+
+ int64_t modifier_;
+ std::unique_ptr<EglDmaBuf> egl_dmabuf_;
+ // List of modifiers we query as supported by the graphics card/driver
+ std::vector<uint64_t> modifiers_;
+
+ // PipeWire types
+ struct pw_context* pw_context_ = nullptr;
+ struct pw_core* pw_core_ = nullptr;
+ struct pw_stream* pw_stream_ = nullptr;
+ struct pw_thread_loop* pw_main_loop_ = nullptr;
+ struct spa_source* renegotiate_ = nullptr;
+
+ spa_hook spa_core_listener_;
+ spa_hook spa_stream_listener_;
+
+ // A number used to verify all previous methods and the resulting
+ // events have been handled.
+ int server_version_sync_ = 0;
+ // Version of the running PipeWire server we communicate with
+ PipeWireVersion pw_server_version_;
+ // Version of the library used to run our code
+ PipeWireVersion pw_client_version_;
+
+ // Resolution parameters.
+ uint32_t width_ = 0;
+ uint32_t height_ = 0;
+ webrtc::Mutex resolution_lock_;
+ // Resolution changes are processed during buffer processing.
+ bool pending_resolution_change_ RTC_GUARDED_BY(&resolution_lock_) = false;
+
+ bool use_damage_region_ = true;
+
+ // Specifies whether the pipewire stream has been initialized with a request
+ // to embed cursor into the captured frames.
+ bool is_cursor_embedded_ = false;
+
+ // event handlers
+ pw_core_events pw_core_events_ = {};
+ pw_stream_events pw_stream_events_ = {};
+
+ struct spa_video_info_raw spa_video_format_;
+
+ void ProcessBuffer(pw_buffer* buffer);
+ bool ProcessMemFDBuffer(pw_buffer* buffer,
+ DesktopFrame& frame,
+ const DesktopVector& offset);
+ bool ProcessDMABuffer(pw_buffer* buffer,
+ DesktopFrame& frame,
+ const DesktopVector& offset);
+ void ConvertRGBxToBGRx(uint8_t* frame, uint32_t size);
+
+ // PipeWire callbacks
+ static void OnCoreError(void* data,
+ uint32_t id,
+ int seq,
+ int res,
+ const char* message);
+ static void OnCoreDone(void* user_data, uint32_t id, int seq);
+ static void OnCoreInfo(void* user_data, const pw_core_info* info);
+ static void OnStreamParamChanged(void* data,
+ uint32_t id,
+ const struct spa_pod* format);
+ static void OnStreamStateChanged(void* data,
+ pw_stream_state old_state,
+ pw_stream_state state,
+ const char* error_message);
+ static void OnStreamProcess(void* data);
+ // This will be invoked in case we fail to process DMA-BUF PW buffer using
+ // negotiated stream parameters (modifier). We will drop the modifier we
+ // failed to use and try to use a different one or fallback to shared memory
+ // buffers.
+ static void OnRenegotiateFormat(void* data, uint64_t);
+};
+
+void SharedScreenCastStreamPrivate::OnCoreError(void* data,
+ uint32_t id,
+ int seq,
+ int res,
+ const char* message) {
+ SharedScreenCastStreamPrivate* stream =
+ static_cast<SharedScreenCastStreamPrivate*>(data);
+ RTC_DCHECK(stream);
+
+ RTC_LOG(LS_ERROR) << "PipeWire remote error: " << message;
+ pw_thread_loop_signal(stream->pw_main_loop_, false);
+}
+
+void SharedScreenCastStreamPrivate::OnCoreInfo(void* data,
+ const pw_core_info* info) {
+ SharedScreenCastStreamPrivate* stream =
+ static_cast<SharedScreenCastStreamPrivate*>(data);
+ RTC_DCHECK(stream);
+
+ stream->pw_server_version_ = PipeWireVersion::Parse(info->version);
+}
+
+void SharedScreenCastStreamPrivate::OnCoreDone(void* data,
+ uint32_t id,
+ int seq) {
+ const SharedScreenCastStreamPrivate* stream =
+ static_cast<SharedScreenCastStreamPrivate*>(data);
+ RTC_DCHECK(stream);
+
+ if (id == PW_ID_CORE && stream->server_version_sync_ == seq) {
+ pw_thread_loop_signal(stream->pw_main_loop_, false);
+ }
+}
+
+// static
+void SharedScreenCastStreamPrivate::OnStreamStateChanged(
+ void* data,
+ pw_stream_state old_state,
+ pw_stream_state state,
+ const char* error_message) {
+ SharedScreenCastStreamPrivate* that =
+ static_cast<SharedScreenCastStreamPrivate*>(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_PAUSED:
+ if (that->observer_ && old_state != PW_STREAM_STATE_STREAMING) {
+ that->observer_->OnStreamConfigured();
+ }
+ break;
+ case PW_STREAM_STATE_STREAMING:
+ case PW_STREAM_STATE_UNCONNECTED:
+ case PW_STREAM_STATE_CONNECTING:
+ break;
+ }
+}
+
+// static
+void SharedScreenCastStreamPrivate::OnStreamParamChanged(
+ void* data,
+ uint32_t id,
+ const struct spa_pod* format) {
+ SharedScreenCastStreamPrivate* that =
+ static_cast<SharedScreenCastStreamPrivate*>(data);
+ RTC_DCHECK(that);
+
+ RTC_LOG(LS_INFO) << "PipeWire stream format changed.";
+ if (!format || id != SPA_PARAM_Format) {
+ return;
+ }
+
+ spa_format_video_raw_parse(format, &that->spa_video_format_);
+
+ 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->stream_size_ = DesktopSize(width, height);
+
+ uint8_t buffer[1024] = {};
+ auto builder = spa_pod_builder{buffer, sizeof(buffer)};
+
+ // Setup buffers and meta header for new format.
+
+ // When SPA_FORMAT_VIDEO_modifier is present we can use DMA-BUFs as
+ // the server announces support for it.
+ // See https://github.com/PipeWire/pipewire/blob/master/doc/dma-buf.dox
+ const bool has_modifier =
+ spa_pod_find_prop(format, nullptr, SPA_FORMAT_VIDEO_modifier);
+ that->modifier_ =
+ has_modifier ? that->spa_video_format_.modifier : DRM_FORMAT_MOD_INVALID;
+ std::vector<const spa_pod*> params;
+ const int buffer_types =
+ has_modifier
+ ? (1 << SPA_DATA_DmaBuf) | (1 << SPA_DATA_MemFd)
+ : (1 << SPA_DATA_MemFd);
+
+ params.push_back(reinterpret_cast<spa_pod*>(spa_pod_builder_add_object(
+ &builder, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers,
+ SPA_PARAM_BUFFERS_size, SPA_POD_Int(size), SPA_PARAM_BUFFERS_stride,
+ SPA_POD_Int(stride), SPA_PARAM_BUFFERS_buffers,
+ SPA_POD_CHOICE_RANGE_Int(8, 1, 32), SPA_PARAM_BUFFERS_dataType,
+ SPA_POD_CHOICE_FLAGS_Int(buffer_types))));
+ params.push_back(reinterpret_cast<spa_pod*>(spa_pod_builder_add_object(
+ &builder, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type,
+ SPA_POD_Id(SPA_META_Header), SPA_PARAM_META_size,
+ SPA_POD_Int(sizeof(struct spa_meta_header)))));
+ params.push_back(reinterpret_cast<spa_pod*>(spa_pod_builder_add_object(
+ &builder, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type,
+ SPA_POD_Id(SPA_META_VideoCrop), SPA_PARAM_META_size,
+ SPA_POD_Int(sizeof(struct spa_meta_region)))));
+ params.push_back(reinterpret_cast<spa_pod*>(spa_pod_builder_add_object(
+ &builder, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type,
+ SPA_POD_Id(SPA_META_Cursor), SPA_PARAM_META_size,
+ SPA_POD_CHOICE_RANGE_Int(CursorMetaSize(64, 64), CursorMetaSize(1, 1),
+ CursorMetaSize(384, 384)))));
+ params.push_back(reinterpret_cast<spa_pod*>(spa_pod_builder_add_object(
+ &builder, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type,
+ SPA_POD_Id(SPA_META_VideoDamage), SPA_PARAM_META_size,
+ SPA_POD_CHOICE_RANGE_Int(
+ sizeof(struct spa_meta_region) * kVideoDamageRegionCount,
+ sizeof(struct spa_meta_region) * 1,
+ sizeof(struct spa_meta_region) * kVideoDamageRegionCount))));
+
+ pw_stream_update_params(that->pw_stream_, params.data(), params.size());
+}
+
+// static
+void SharedScreenCastStreamPrivate::OnStreamProcess(void* data) {
+ SharedScreenCastStreamPrivate* that =
+ static_cast<SharedScreenCastStreamPrivate*>(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->ProcessBuffer(buffer);
+
+ pw_stream_queue_buffer(that->pw_stream_, buffer);
+}
+
+void SharedScreenCastStreamPrivate::OnRenegotiateFormat(void* data, uint64_t) {
+ SharedScreenCastStreamPrivate* that =
+ static_cast<SharedScreenCastStreamPrivate*>(data);
+ RTC_DCHECK(that);
+
+ {
+ PipeWireThreadLoopLock thread_loop_lock(that->pw_main_loop_);
+
+ uint8_t buffer[2048] = {};
+
+ spa_pod_builder builder = spa_pod_builder{buffer, sizeof(buffer)};
+
+ std::vector<const spa_pod*> params;
+ struct spa_rectangle resolution =
+ SPA_RECTANGLE(that->width_, that->height_);
+
+ webrtc::MutexLock lock(&that->resolution_lock_);
+ for (uint32_t format : {SPA_VIDEO_FORMAT_BGRA, SPA_VIDEO_FORMAT_RGBA,
+ SPA_VIDEO_FORMAT_BGRx, SPA_VIDEO_FORMAT_RGBx}) {
+ if (!that->modifiers_.empty()) {
+ params.push_back(BuildFormat(
+ &builder, format, that->modifiers_,
+ that->pending_resolution_change_ ? &resolution : nullptr));
+ }
+ params.push_back(BuildFormat(
+ &builder, format, /*modifiers=*/{},
+ that->pending_resolution_change_ ? &resolution : nullptr));
+ }
+
+ pw_stream_update_params(that->pw_stream_, params.data(), params.size());
+ that->pending_resolution_change_ = false;
+ }
+}
+
+SharedScreenCastStreamPrivate::SharedScreenCastStreamPrivate() {}
+
+SharedScreenCastStreamPrivate::~SharedScreenCastStreamPrivate() {
+ StopAndCleanupStream();
+}
+
+RTC_NO_SANITIZE("cfi-icall")
+bool SharedScreenCastStreamPrivate::StartScreenCastStream(
+ uint32_t stream_node_id,
+ int fd,
+ uint32_t width,
+ uint32_t height,
+ bool is_cursor_embedded) {
+ width_ = width;
+ height_ = height;
+ is_cursor_embedded_ = is_cursor_embedded;
+ if (!InitializePipeWire()) {
+ RTC_LOG(LS_ERROR) << "Unable to open PipeWire library";
+ return false;
+ }
+ egl_dmabuf_ = std::make_unique<EglDmaBuf>();
+
+ pw_stream_node_id_ = stream_node_id;
+
+ pw_init(/*argc=*/nullptr, /*argc=*/nullptr);
+
+ pw_main_loop_ = pw_thread_loop_new("pipewire-main-loop", nullptr);
+
+ pw_context_ =
+ pw_context_new(pw_thread_loop_get_loop(pw_main_loop_), nullptr, 0);
+ if (!pw_context_) {
+ RTC_LOG(LS_ERROR) << "Failed to create PipeWire context";
+ return false;
+ }
+
+ if (pw_thread_loop_start(pw_main_loop_) < 0) {
+ RTC_LOG(LS_ERROR) << "Failed to start main PipeWire loop";
+ return false;
+ }
+
+ pw_client_version_ = PipeWireVersion::Parse(pw_get_library_version());
+
+ // Initialize event handlers, remote end and stream-related.
+ pw_core_events_.version = PW_VERSION_CORE_EVENTS;
+ pw_core_events_.info = &OnCoreInfo;
+ pw_core_events_.done = &OnCoreDone;
+ pw_core_events_.error = &OnCoreError;
+
+ pw_stream_events_.version = PW_VERSION_STREAM_EVENTS;
+ pw_stream_events_.state_changed = &OnStreamStateChanged;
+ pw_stream_events_.param_changed = &OnStreamParamChanged;
+ pw_stream_events_.process = &OnStreamProcess;
+
+ {
+ PipeWireThreadLoopLock thread_loop_lock(pw_main_loop_);
+
+ if (fd >= 0) {
+ pw_core_ = pw_context_connect_fd(
+ pw_context_, fcntl(fd, F_DUPFD_CLOEXEC, 0), nullptr, 0);
+ } else {
+ pw_core_ = pw_context_connect(pw_context_, nullptr, 0);
+ }
+
+ if (!pw_core_) {
+ RTC_LOG(LS_ERROR) << "Failed to connect PipeWire context";
+ return false;
+ }
+
+ pw_core_add_listener(pw_core_, &spa_core_listener_, &pw_core_events_, this);
+
+ // Add an event that can be later invoked by pw_loop_signal_event()
+ renegotiate_ = pw_loop_add_event(pw_thread_loop_get_loop(pw_main_loop_),
+ OnRenegotiateFormat, this);
+
+ server_version_sync_ =
+ pw_core_sync(pw_core_, PW_ID_CORE, server_version_sync_);
+
+ pw_thread_loop_wait(pw_main_loop_);
+
+ pw_properties* reuseProps =
+ pw_properties_new_string("pipewire.client.reuse=1");
+ pw_stream_ = pw_stream_new(pw_core_, "webrtc-consume-stream", reuseProps);
+
+ if (!pw_stream_) {
+ RTC_LOG(LS_ERROR) << "Failed to create PipeWire stream";
+ return false;
+ }
+
+ pw_stream_add_listener(pw_stream_, &spa_stream_listener_,
+ &pw_stream_events_, this);
+ uint8_t buffer[2048] = {};
+
+ spa_pod_builder builder = spa_pod_builder{buffer, sizeof(buffer)};
+
+ std::vector<const spa_pod*> params;
+ const bool has_required_pw_client_version =
+ pw_client_version_ >= kDmaBufModifierMinVersion;
+ const bool has_required_pw_server_version =
+ pw_server_version_ >= kDmaBufModifierMinVersion;
+ struct spa_rectangle resolution;
+ bool set_resolution = false;
+ if (width && height) {
+ resolution = SPA_RECTANGLE(width, height);
+ set_resolution = true;
+ }
+ for (uint32_t format : {SPA_VIDEO_FORMAT_BGRA, SPA_VIDEO_FORMAT_RGBA,
+ SPA_VIDEO_FORMAT_BGRx, SPA_VIDEO_FORMAT_RGBx}) {
+ // Modifiers can be used with PipeWire >= 0.3.33
+ if (has_required_pw_client_version && has_required_pw_server_version) {
+ modifiers_ = egl_dmabuf_->QueryDmaBufModifiers(format);
+
+ if (!modifiers_.empty()) {
+ params.push_back(BuildFormat(&builder, format, modifiers_,
+ set_resolution ? &resolution : nullptr));
+ }
+ }
+
+ params.push_back(BuildFormat(&builder, format, /*modifiers=*/{},
+ set_resolution ? &resolution : nullptr));
+ }
+
+ if (pw_stream_connect(pw_stream_, PW_DIRECTION_INPUT, pw_stream_node_id_,
+ PW_STREAM_FLAG_AUTOCONNECT, params.data(),
+ params.size()) != 0) {
+ RTC_LOG(LS_ERROR) << "Could not connect receiving stream.";
+ return false;
+ }
+
+ RTC_LOG(LS_INFO) << "PipeWire remote opened.";
+ }
+ return true;
+}
+
+RTC_NO_SANITIZE("cfi-icall")
+void SharedScreenCastStreamPrivate::UpdateScreenCastStreamResolution(
+ uint32_t width,
+ uint32_t height) {
+ if (!width || !height) {
+ RTC_LOG(LS_WARNING) << "Bad resolution specified: " << width << "x"
+ << height;
+ return;
+ }
+ if (!pw_main_loop_) {
+ RTC_LOG(LS_WARNING) << "No main pipewire loop, ignoring resolution change";
+ return;
+ }
+ if (!renegotiate_) {
+ RTC_LOG(LS_WARNING) << "Can not renegotiate stream params, ignoring "
+ << "resolution change";
+ return;
+ }
+ if (width_ != width || height_ != height) {
+ width_ = width;
+ height_ = height;
+ {
+ webrtc::MutexLock lock(&resolution_lock_);
+ pending_resolution_change_ = true;
+ }
+ pw_loop_signal_event(pw_thread_loop_get_loop(pw_main_loop_), renegotiate_);
+ }
+}
+
+void SharedScreenCastStreamPrivate::StopScreenCastStream() {
+ StopAndCleanupStream();
+}
+
+void SharedScreenCastStreamPrivate::StopAndCleanupStream() {
+ // We get buffers on the PipeWire thread, but this is called from the capturer
+ // thread, so we need to wait on and stop the pipewire thread before we
+ // disconnect the stream so that we can guarantee we aren't in the middle of
+ // processing a new frame.
+
+ // Even if we *do* somehow have the other objects without a pipewire thread,
+ // destroying them without a thread causes a crash.
+ if (!pw_main_loop_)
+ return;
+
+ // While we can stop the thread now, we cannot destroy it until we've cleaned
+ // up the other members.
+ pw_thread_loop_wait(pw_main_loop_);
+ pw_thread_loop_stop(pw_main_loop_);
+
+ if (pw_stream_) {
+ pw_stream_disconnect(pw_stream_);
+ pw_stream_destroy(pw_stream_);
+ pw_stream_ = nullptr;
+
+ {
+ webrtc::MutexLock lock(&queue_lock_);
+ queue_.Reset();
+ }
+ }
+
+ if (pw_core_) {
+ pw_core_disconnect(pw_core_);
+ pw_core_ = nullptr;
+ }
+
+ if (pw_context_) {
+ pw_context_destroy(pw_context_);
+ pw_context_ = nullptr;
+ }
+
+ pw_thread_loop_destroy(pw_main_loop_);
+ pw_main_loop_ = nullptr;
+}
+
+std::unique_ptr<SharedDesktopFrame>
+SharedScreenCastStreamPrivate::CaptureFrame() {
+ webrtc::MutexLock lock(&queue_lock_);
+
+ if (!pw_stream_ || !queue_.current_frame()) {
+ return std::unique_ptr<SharedDesktopFrame>{};
+ }
+
+ std::unique_ptr<SharedDesktopFrame> frame = queue_.current_frame()->Share();
+ if (use_damage_region_) {
+ frame->mutable_updated_region()->Swap(&damage_region_);
+ damage_region_.Clear();
+ }
+
+ return frame;
+}
+
+std::unique_ptr<MouseCursor> SharedScreenCastStreamPrivate::CaptureCursor() {
+ if (!mouse_cursor_) {
+ return nullptr;
+ }
+
+ return std::move(mouse_cursor_);
+}
+
+DesktopVector SharedScreenCastStreamPrivate::CaptureCursorPosition() {
+ return mouse_cursor_position_;
+}
+
+RTC_NO_SANITIZE("cfi-icall")
+void SharedScreenCastStreamPrivate::ProcessBuffer(pw_buffer* buffer) {
+ spa_buffer* spa_buffer = buffer->buffer;
+
+ // Try to update the mouse cursor first, because it can be the only
+ // information carried by the buffer
+ {
+ const struct spa_meta_cursor* cursor =
+ static_cast<struct spa_meta_cursor*>(spa_buffer_find_meta_data(
+ spa_buffer, SPA_META_Cursor, sizeof(*cursor)));
+ if (cursor && spa_meta_cursor_is_valid(cursor)) {
+ struct spa_meta_bitmap* bitmap = nullptr;
+
+ if (cursor->bitmap_offset)
+ bitmap =
+ SPA_MEMBER(cursor, cursor->bitmap_offset, struct spa_meta_bitmap);
+
+ if (bitmap && bitmap->size.width > 0 && bitmap->size.height > 0) {
+ const uint8_t* bitmap_data =
+ SPA_MEMBER(bitmap, bitmap->offset, uint8_t);
+ BasicDesktopFrame* mouse_frame = new BasicDesktopFrame(
+ DesktopSize(bitmap->size.width, bitmap->size.height));
+ mouse_frame->CopyPixelsFrom(
+ bitmap_data, bitmap->stride,
+ DesktopRect::MakeWH(bitmap->size.width, bitmap->size.height));
+ mouse_cursor_ = std::make_unique<MouseCursor>(
+ mouse_frame, DesktopVector(cursor->hotspot.x, cursor->hotspot.y));
+
+ if (observer_) {
+ observer_->OnCursorShapeChanged();
+ }
+ }
+ mouse_cursor_position_.set(cursor->position.x, cursor->position.y);
+
+ if (observer_) {
+ observer_->OnCursorPositionChanged();
+ }
+ }
+ }
+
+ if (spa_buffer->datas[0].chunk->size == 0) {
+ return;
+ }
+
+ // Use SPA_META_VideoCrop metadata to get the frame size. KDE and GNOME do
+ // handle screen/window sharing differently. KDE/KWin doesn't use
+ // SPA_META_VideoCrop metadata and when sharing a window, it always sets
+ // stream size to size of the window. With that we just allocate the
+ // DesktopFrame using the size of the stream itself. GNOME/Mutter
+ // always sets stream size to the size of the whole screen, even when sharing
+ // a window. To get the real window size we have to use SPA_META_VideoCrop
+ // metadata. This gives us the size we need in order to allocate the
+ // DesktopFrame.
+
+ struct spa_meta_region* videocrop_metadata =
+ static_cast<struct spa_meta_region*>(spa_buffer_find_meta_data(
+ spa_buffer, SPA_META_VideoCrop, sizeof(*videocrop_metadata)));
+
+ // Video size from metadata is bigger than an actual video stream size.
+ // The metadata are wrong or we should up-scale the video...in both cases
+ // just quit now.
+ if (videocrop_metadata &&
+ (videocrop_metadata->region.size.width >
+ static_cast<uint32_t>(stream_size_.width()) ||
+ videocrop_metadata->region.size.height >
+ static_cast<uint32_t>(stream_size_.height()))) {
+ RTC_LOG(LS_ERROR) << "Stream metadata sizes are wrong!";
+
+ if (observer_) {
+ observer_->OnFailedToProcessBuffer();
+ }
+
+ return;
+ }
+
+ // Use SPA_META_VideoCrop metadata to get the DesktopFrame size in case
+ // a windows is shared and it represents just a small portion of the
+ // stream itself. This will be for example used in case of GNOME (Mutter)
+ // where the stream will have the size of the screen itself, but we care
+ // only about smaller portion representing the window inside.
+ bool videocrop_metadata_use = false;
+ const struct spa_rectangle* videocrop_metadata_size =
+ videocrop_metadata ? &videocrop_metadata->region.size : nullptr;
+
+ if (videocrop_metadata_size && videocrop_metadata_size->width != 0 &&
+ videocrop_metadata_size->height != 0 &&
+ (static_cast<int>(videocrop_metadata_size->width) <
+ stream_size_.width() ||
+ static_cast<int>(videocrop_metadata_size->height) <
+ stream_size_.height())) {
+ videocrop_metadata_use = true;
+ }
+
+ if (videocrop_metadata_use) {
+ frame_size_ = DesktopSize(videocrop_metadata_size->width,
+ videocrop_metadata_size->height);
+ } else {
+ frame_size_ = stream_size_;
+ }
+
+ // Get the position of the video crop within the stream. Just double-check
+ // that the position doesn't exceed the size of the stream itself.
+ // NOTE: Currently it looks there is no implementation using this.
+ uint32_t y_offset =
+ videocrop_metadata_use &&
+ (videocrop_metadata->region.position.y + frame_size_.height() <=
+ stream_size_.height())
+ ? videocrop_metadata->region.position.y
+ : 0;
+ uint32_t x_offset =
+ videocrop_metadata_use &&
+ (videocrop_metadata->region.position.x + frame_size_.width() <=
+ stream_size_.width())
+ ? videocrop_metadata->region.position.x
+ : 0;
+ DesktopVector offset = DesktopVector(x_offset, y_offset);
+
+ webrtc::MutexLock lock(&queue_lock_);
+
+ queue_.MoveToNextFrame();
+ if (queue_.current_frame() && queue_.current_frame()->IsShared()) {
+ RTC_DLOG(LS_WARNING) << "Overwriting frame that is still shared";
+
+ if (observer_) {
+ observer_->OnFailedToProcessBuffer();
+ }
+ }
+
+ if (!queue_.current_frame() ||
+ !queue_.current_frame()->size().equals(frame_size_)) {
+ std::unique_ptr<DesktopFrame> frame(new BasicDesktopFrame(
+ DesktopSize(frame_size_.width(), frame_size_.height())));
+ queue_.ReplaceCurrentFrame(SharedDesktopFrame::Wrap(std::move(frame)));
+ }
+
+ bool bufferProcessed = false;
+ if (spa_buffer->datas[0].type == SPA_DATA_MemFd) {
+ bufferProcessed =
+ ProcessMemFDBuffer(buffer, *queue_.current_frame(), offset);
+ } else if (spa_buffer->datas[0].type == SPA_DATA_DmaBuf) {
+ bufferProcessed = ProcessDMABuffer(buffer, *queue_.current_frame(), offset);
+ }
+
+ if (!bufferProcessed) {
+ if (observer_) {
+ observer_->OnFailedToProcessBuffer();
+ }
+ return;
+ }
+
+ if (spa_video_format_.format == SPA_VIDEO_FORMAT_RGBx ||
+ spa_video_format_.format == SPA_VIDEO_FORMAT_RGBA) {
+ uint8_t* tmp_src = queue_.current_frame()->data();
+ for (int i = 0; i < frame_size_.height(); ++i) {
+ // If both sides decided to go with the RGBx format we need to convert
+ // it to BGRx to match color format expected by WebRTC.
+ ConvertRGBxToBGRx(tmp_src, queue_.current_frame()->stride());
+ tmp_src += queue_.current_frame()->stride();
+ }
+ }
+
+ if (observer_) {
+ observer_->OnDesktopFrameChanged();
+ }
+
+ if (use_damage_region_) {
+ const struct spa_meta* video_damage = static_cast<struct spa_meta*>(
+ spa_buffer_find_meta(spa_buffer, SPA_META_VideoDamage));
+ if (video_damage) {
+ spa_meta_region* meta_region;
+
+ queue_.current_frame()->mutable_updated_region()->Clear();
+
+ spa_meta_for_each(meta_region, video_damage) {
+ // Skip empty regions
+ if (meta_region->region.size.width == 0 ||
+ meta_region->region.size.height == 0) {
+ continue;
+ }
+
+ damage_region_.AddRect(DesktopRect::MakeXYWH(
+ meta_region->region.position.x, meta_region->region.position.y,
+ meta_region->region.size.width, meta_region->region.size.height));
+ }
+ } else {
+ damage_region_.SetRect(
+ DesktopRect::MakeSize(queue_.current_frame()->size()));
+ }
+ } else {
+ queue_.current_frame()->mutable_updated_region()->SetRect(
+ DesktopRect::MakeSize(queue_.current_frame()->size()));
+ }
+ queue_.current_frame()->set_may_contain_cursor(is_cursor_embedded_);
+}
+
+RTC_NO_SANITIZE("cfi-icall")
+bool SharedScreenCastStreamPrivate::ProcessMemFDBuffer(
+ pw_buffer* buffer,
+ DesktopFrame& frame,
+ const DesktopVector& offset) {
+ spa_buffer* spa_buffer = buffer->buffer;
+ ScopedBuf map;
+ uint8_t* src = nullptr;
+
+ map.initialize(
+ static_cast<uint8_t*>(
+ mmap(nullptr,
+ spa_buffer->datas[0].maxsize + spa_buffer->datas[0].mapoffset,
+ PROT_READ, MAP_PRIVATE, spa_buffer->datas[0].fd, 0)),
+ spa_buffer->datas[0].maxsize + spa_buffer->datas[0].mapoffset,
+ spa_buffer->datas[0].fd);
+
+ if (!map) {
+ RTC_LOG(LS_ERROR) << "Failed to mmap the memory: " << std::strerror(errno);
+ return false;
+ }
+
+ src = SPA_MEMBER(map.get(), spa_buffer->datas[0].mapoffset, uint8_t);
+
+ uint32_t buffer_stride = spa_buffer->datas[0].chunk->stride;
+ uint32_t src_stride = buffer_stride;
+
+ uint8_t* updated_src =
+ src + (src_stride * offset.y()) + (kBytesPerPixel * offset.x());
+
+ frame.CopyPixelsFrom(
+ updated_src, (src_stride - (kBytesPerPixel * offset.x())),
+ DesktopRect::MakeWH(frame.size().width(), frame.size().height()));
+
+ return true;
+}
+
+RTC_NO_SANITIZE("cfi-icall")
+bool SharedScreenCastStreamPrivate::ProcessDMABuffer(
+ pw_buffer* buffer,
+ DesktopFrame& frame,
+ const DesktopVector& offset) {
+ spa_buffer* spa_buffer = buffer->buffer;
+
+ const uint n_planes = spa_buffer->n_datas;
+
+ if (!n_planes) {
+ return false;
+ }
+
+ std::vector<EglDmaBuf::PlaneData> plane_datas;
+ for (uint32_t i = 0; i < n_planes; ++i) {
+ EglDmaBuf::PlaneData data = {
+ static_cast<int32_t>(spa_buffer->datas[i].fd),
+ static_cast<uint32_t>(spa_buffer->datas[i].chunk->stride),
+ static_cast<uint32_t>(spa_buffer->datas[i].chunk->offset)};
+ plane_datas.push_back(data);
+ }
+
+ const bool imported = egl_dmabuf_->ImageFromDmaBuf(
+ stream_size_, spa_video_format_.format, plane_datas, modifier_, offset,
+ frame.size(), frame.data());
+ if (!imported) {
+ RTC_LOG(LS_ERROR) << "Dropping DMA-BUF modifier: " << modifier_
+ << " and trying to renegotiate stream parameters";
+
+ if (pw_server_version_ >= kDropSingleModifierMinVersion) {
+ modifiers_.erase(
+ std::remove(modifiers_.begin(), modifiers_.end(), modifier_),
+ modifiers_.end());
+ } else {
+ modifiers_.clear();
+ }
+
+ pw_loop_signal_event(pw_thread_loop_get_loop(pw_main_loop_), renegotiate_);
+ return false;
+ }
+
+ return true;
+}
+
+void SharedScreenCastStreamPrivate::ConvertRGBxToBGRx(uint8_t* frame,
+ uint32_t size) {
+ 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;
+ }
+}
+
+SharedScreenCastStream::SharedScreenCastStream()
+ : private_(std::make_unique<SharedScreenCastStreamPrivate>()) {}
+
+SharedScreenCastStream::~SharedScreenCastStream() {}
+
+rtc::scoped_refptr<SharedScreenCastStream>
+SharedScreenCastStream::CreateDefault() {
+ // Explicit new, to access non-public constructor.
+ return rtc::scoped_refptr<SharedScreenCastStream>(
+ new SharedScreenCastStream());
+}
+
+bool SharedScreenCastStream::StartScreenCastStream(uint32_t stream_node_id) {
+ return private_->StartScreenCastStream(stream_node_id, -1);
+}
+
+bool SharedScreenCastStream::StartScreenCastStream(uint32_t stream_node_id,
+ int fd,
+ uint32_t width,
+ uint32_t height,
+ bool is_cursor_embedded) {
+ return private_->StartScreenCastStream(stream_node_id, fd, width, height,
+ is_cursor_embedded);
+}
+
+void SharedScreenCastStream::UpdateScreenCastStreamResolution(uint32_t width,
+ uint32_t height) {
+ private_->UpdateScreenCastStreamResolution(width, height);
+}
+
+void SharedScreenCastStream::SetUseDamageRegion(bool use_damage_region) {
+ private_->SetUseDamageRegion(use_damage_region);
+}
+
+void SharedScreenCastStream::SetObserver(
+ SharedScreenCastStream::Observer* observer) {
+ private_->SetObserver(observer);
+}
+
+void SharedScreenCastStream::StopScreenCastStream() {
+ private_->StopScreenCastStream();
+}
+
+std::unique_ptr<SharedDesktopFrame> SharedScreenCastStream::CaptureFrame() {
+ return private_->CaptureFrame();
+}
+
+std::unique_ptr<MouseCursor> SharedScreenCastStream::CaptureCursor() {
+ return private_->CaptureCursor();
+}
+
+absl::optional<DesktopVector> SharedScreenCastStream::CaptureCursorPosition() {
+ DesktopVector position = private_->CaptureCursorPosition();
+
+ // Consider only (x >= 0 and y >= 0) a valid position
+ if (position.x() < 0 || position.y() < 0) {
+ return absl::nullopt;
+ }
+
+ return position;
+}
+
+} // namespace webrtc
diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/wayland/shared_screencast_stream.h b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/shared_screencast_stream.h
new file mode 100644
index 0000000000..9cdd3d89be
--- /dev/null
+++ b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/shared_screencast_stream.h
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2022 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_SHARED_SCREENCAST_STREAM_H_
+#define MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_SHARED_SCREENCAST_STREAM_H_
+
+#include <memory>
+
+#include "absl/types/optional.h"
+#include "api/ref_counted_base.h"
+#include "api/scoped_refptr.h"
+#include "modules/desktop_capture/mouse_cursor.h"
+#include "modules/desktop_capture/screen_capture_frame_queue.h"
+#include "modules/desktop_capture/shared_desktop_frame.h"
+#include "rtc_base/system/rtc_export.h"
+
+namespace webrtc {
+
+class SharedScreenCastStreamPrivate;
+
+class RTC_EXPORT SharedScreenCastStream
+ : public rtc::RefCountedNonVirtual<SharedScreenCastStream> {
+ public:
+ class Observer {
+ public:
+ virtual void OnCursorPositionChanged() = 0;
+ virtual void OnCursorShapeChanged() = 0;
+ virtual void OnDesktopFrameChanged() = 0;
+ virtual void OnFailedToProcessBuffer() = 0;
+ virtual void OnStreamConfigured() = 0;
+
+ protected:
+ Observer() = default;
+ virtual ~Observer() = default;
+ };
+
+ static rtc::scoped_refptr<SharedScreenCastStream> CreateDefault();
+
+ bool StartScreenCastStream(uint32_t stream_node_id);
+ bool StartScreenCastStream(uint32_t stream_node_id,
+ int fd,
+ uint32_t width = 0,
+ uint32_t height = 0,
+ bool is_cursor_embedded = false);
+ void UpdateScreenCastStreamResolution(uint32_t width, uint32_t height);
+ void SetUseDamageRegion(bool use_damage_region);
+ void SetObserver(SharedScreenCastStream::Observer* observer);
+ void StopScreenCastStream();
+
+ // Below functions return the most recent information we get from a
+ // PipeWire buffer on each Process() callback. This assumes that we
+ // managed to successfuly connect to a PipeWire stream provided by the
+ // compositor (based on stream parameters). The cursor data are obtained
+ // from spa_meta_cursor stream metadata and therefore the cursor is not
+ // part of actual screen/window frame.
+
+ // Returns the most recent screen/window frame we obtained from PipeWire
+ // buffer. Will return an empty frame in case we didn't manage to get a frame
+ // from PipeWire buffer.
+ std::unique_ptr<SharedDesktopFrame> CaptureFrame();
+
+ // Returns the most recent mouse cursor image. Will return an nullptr cursor
+ // in case we didn't manage to get a cursor from PipeWire buffer. NOTE: the
+ // cursor image might not be updated on every cursor location change, but
+ // actually only when its shape changes.
+ std::unique_ptr<MouseCursor> CaptureCursor();
+
+ // Returns the most recent mouse cursor position. Will not return a value in
+ // case we didn't manage to get it from PipeWire buffer.
+ absl::optional<DesktopVector> CaptureCursorPosition();
+
+ ~SharedScreenCastStream();
+
+ protected:
+ SharedScreenCastStream();
+
+ private:
+ friend class SharedScreenCastStreamPrivate;
+
+ SharedScreenCastStream(const SharedScreenCastStream&) = delete;
+ SharedScreenCastStream& operator=(const SharedScreenCastStream&) = delete;
+
+ std::unique_ptr<SharedScreenCastStreamPrivate> private_;
+};
+
+} // namespace webrtc
+
+#endif // MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_SHARED_SCREENCAST_STREAM_H_
diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/wayland/shared_screencast_stream_unittest.cc b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/shared_screencast_stream_unittest.cc
new file mode 100644
index 0000000000..1de5f19013
--- /dev/null
+++ b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/shared_screencast_stream_unittest.cc
@@ -0,0 +1,160 @@
+/*
+ * Copyright (c) 2022 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "modules/desktop_capture/linux/wayland/shared_screencast_stream.h"
+
+#include <memory>
+#include <utility>
+
+#include "api/units/time_delta.h"
+#include "modules/desktop_capture/desktop_capturer.h"
+#include "modules/desktop_capture/desktop_frame.h"
+#include "modules/desktop_capture/linux/wayland/test/test_screencast_stream_provider.h"
+#include "modules/desktop_capture/rgba_color.h"
+#include "rtc_base/event.h"
+#include "test/gmock.h"
+#include "test/gtest.h"
+
+using ::testing::_;
+using ::testing::AtLeast;
+using ::testing::Ge;
+using ::testing::Invoke;
+
+namespace webrtc {
+
+constexpr TimeDelta kShortWait = TimeDelta::Seconds(5);
+constexpr TimeDelta kLongWait = TimeDelta::Seconds(15);
+
+constexpr int kBytesPerPixel = 4;
+constexpr int32_t kWidth = 800;
+constexpr int32_t kHeight = 640;
+
+class PipeWireStreamTest : public ::testing::Test,
+ public TestScreenCastStreamProvider::Observer,
+ public SharedScreenCastStream::Observer {
+ public:
+ PipeWireStreamTest() = default;
+ ~PipeWireStreamTest() = default;
+
+ // FakeScreenCastPortal::Observer
+ MOCK_METHOD(void, OnBufferAdded, (), (override));
+ MOCK_METHOD(void, OnFrameRecorded, (), (override));
+ MOCK_METHOD(void, OnStreamReady, (uint32_t stream_node_id), (override));
+ MOCK_METHOD(void, OnStartStreaming, (), (override));
+ MOCK_METHOD(void, OnStopStreaming, (), (override));
+
+ // SharedScreenCastStream::Observer
+ MOCK_METHOD(void, OnCursorPositionChanged, (), (override));
+ MOCK_METHOD(void, OnCursorShapeChanged, (), (override));
+ MOCK_METHOD(void, OnDesktopFrameChanged, (), (override));
+ MOCK_METHOD(void, OnFailedToProcessBuffer, (), (override));
+ MOCK_METHOD(void, OnStreamConfigured, (), (override));
+
+ void SetUp() override {
+ shared_screencast_stream_ = SharedScreenCastStream::CreateDefault();
+ shared_screencast_stream_->SetObserver(this);
+ test_screencast_stream_provider_ =
+ std::make_unique<TestScreenCastStreamProvider>(this, kWidth, kHeight);
+ }
+
+ void StartScreenCastStream(uint32_t stream_node_id) {
+ shared_screencast_stream_->StartScreenCastStream(stream_node_id);
+ }
+
+ protected:
+ uint recorded_frames_ = 0;
+ bool streaming_ = false;
+ std::unique_ptr<TestScreenCastStreamProvider>
+ test_screencast_stream_provider_;
+ rtc::scoped_refptr<SharedScreenCastStream> shared_screencast_stream_;
+};
+
+TEST_F(PipeWireStreamTest, TestPipeWire) {
+ // Set expectations for PipeWire to successfully connect both streams
+ rtc::Event waitConnectEvent;
+ rtc::Event waitStartStreamingEvent;
+
+ EXPECT_CALL(*this, OnStreamReady(_))
+ .WillOnce(Invoke(this, &PipeWireStreamTest::StartScreenCastStream));
+ EXPECT_CALL(*this, OnStreamConfigured).WillOnce([&waitConnectEvent] {
+ waitConnectEvent.Set();
+ });
+ EXPECT_CALL(*this, OnBufferAdded).Times(AtLeast(3));
+ EXPECT_CALL(*this, OnStartStreaming).WillOnce([&waitStartStreamingEvent] {
+ waitStartStreamingEvent.Set();
+ });
+
+ // Give it some time to connect, the order between these shouldn't matter, but
+ // we need to be sure we are connected before we proceed to work with frames.
+ waitConnectEvent.Wait(kLongWait);
+
+ // Wait until we start streaming
+ waitStartStreamingEvent.Wait(kShortWait);
+
+ rtc::Event frameRetrievedEvent;
+ EXPECT_CALL(*this, OnFrameRecorded).Times(3);
+ EXPECT_CALL(*this, OnDesktopFrameChanged)
+ .WillRepeatedly([&frameRetrievedEvent] { frameRetrievedEvent.Set(); });
+
+ // Record a frame in FakePipeWireStream
+ RgbaColor red_color(0, 0, 255);
+ test_screencast_stream_provider_->RecordFrame(red_color);
+
+ // Retrieve a frame from SharedScreenCastStream
+ frameRetrievedEvent.Wait(kShortWait);
+ std::unique_ptr<SharedDesktopFrame> frame =
+ shared_screencast_stream_->CaptureFrame();
+
+ // Check frame parameters
+ ASSERT_NE(frame, nullptr);
+ ASSERT_NE(frame->data(), nullptr);
+ EXPECT_EQ(frame->rect().width(), kWidth);
+ EXPECT_EQ(frame->rect().height(), kHeight);
+ EXPECT_EQ(frame->stride(), frame->rect().width() * kBytesPerPixel);
+ EXPECT_EQ(RgbaColor(frame->data()), red_color);
+
+ // Test DesktopFrameQueue
+ RgbaColor green_color(0, 255, 0);
+ test_screencast_stream_provider_->RecordFrame(green_color);
+ frameRetrievedEvent.Wait(kShortWait);
+ std::unique_ptr<SharedDesktopFrame> frame2 =
+ shared_screencast_stream_->CaptureFrame();
+ ASSERT_NE(frame2, nullptr);
+ ASSERT_NE(frame2->data(), nullptr);
+ EXPECT_EQ(frame2->rect().width(), kWidth);
+ EXPECT_EQ(frame2->rect().height(), kHeight);
+ EXPECT_EQ(frame2->stride(), frame->rect().width() * kBytesPerPixel);
+ EXPECT_EQ(RgbaColor(frame2->data()), green_color);
+
+ // Thanks to DesktopFrameQueue we should be able to have two frames shared
+ EXPECT_EQ(frame->IsShared(), true);
+ EXPECT_EQ(frame2->IsShared(), true);
+ EXPECT_NE(frame->data(), frame2->data());
+
+ // This should result into overwriting a frame in use
+ rtc::Event frameRecordedEvent;
+ RgbaColor blue_color(255, 0, 0);
+ EXPECT_CALL(*this, OnFailedToProcessBuffer).WillOnce([&frameRecordedEvent] {
+ frameRecordedEvent.Set();
+ });
+
+ test_screencast_stream_provider_->RecordFrame(blue_color);
+ frameRecordedEvent.Wait(kShortWait);
+
+ // First frame should be now overwritten with blue color
+ frameRetrievedEvent.Wait(kShortWait);
+ EXPECT_EQ(RgbaColor(frame->data()), blue_color);
+
+ // Test disconnection from stream
+ EXPECT_CALL(*this, OnStopStreaming);
+ shared_screencast_stream_->StopScreenCastStream();
+}
+
+} // namespace webrtc
diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/wayland/test/test_screencast_stream_provider.cc b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/test/test_screencast_stream_provider.cc
new file mode 100644
index 0000000000..3b829959ac
--- /dev/null
+++ b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/test/test_screencast_stream_provider.cc
@@ -0,0 +1,361 @@
+
+/*
+ * Copyright 2022 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "modules/desktop_capture/linux/wayland/test/test_screencast_stream_provider.h"
+
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "modules/portal/pipewire_utils.h"
+#include "rtc_base/logging.h"
+
+namespace webrtc {
+
+constexpr int kBytesPerPixel = 4;
+
+TestScreenCastStreamProvider::TestScreenCastStreamProvider(Observer* observer,
+ uint32_t width,
+ uint32_t height)
+ : observer_(observer), width_(width), height_(height) {
+ if (!InitializePipeWire()) {
+ RTC_LOG(LS_ERROR) << "Unable to open PipeWire";
+ return;
+ }
+
+ pw_init(/*argc=*/nullptr, /*argc=*/nullptr);
+
+ pw_main_loop_ = pw_thread_loop_new("pipewire-test-main-loop", nullptr);
+
+ pw_context_ =
+ pw_context_new(pw_thread_loop_get_loop(pw_main_loop_), nullptr, 0);
+ if (!pw_context_) {
+ RTC_LOG(LS_ERROR) << "PipeWire test: Failed to create PipeWire context";
+ return;
+ }
+
+ if (pw_thread_loop_start(pw_main_loop_) < 0) {
+ RTC_LOG(LS_ERROR) << "PipeWire test: Failed to start main PipeWire loop";
+ return;
+ }
+
+ // Initialize event handlers, remote end and stream-related.
+ pw_core_events_.version = PW_VERSION_CORE_EVENTS;
+ pw_core_events_.error = &OnCoreError;
+
+ pw_stream_events_.version = PW_VERSION_STREAM_EVENTS;
+ pw_stream_events_.add_buffer = &OnStreamAddBuffer;
+ pw_stream_events_.remove_buffer = &OnStreamRemoveBuffer;
+ pw_stream_events_.state_changed = &OnStreamStateChanged;
+ pw_stream_events_.param_changed = &OnStreamParamChanged;
+
+ {
+ PipeWireThreadLoopLock thread_loop_lock(pw_main_loop_);
+
+ pw_core_ = pw_context_connect(pw_context_, nullptr, 0);
+ if (!pw_core_) {
+ RTC_LOG(LS_ERROR) << "PipeWire test: Failed to connect PipeWire context";
+ return;
+ }
+
+ pw_core_add_listener(pw_core_, &spa_core_listener_, &pw_core_events_, this);
+
+ pw_stream_ = pw_stream_new(pw_core_, "webrtc-test-stream", nullptr);
+
+ if (!pw_stream_) {
+ RTC_LOG(LS_ERROR) << "PipeWire test: Failed to create PipeWire stream";
+ return;
+ }
+
+ pw_stream_add_listener(pw_stream_, &spa_stream_listener_,
+ &pw_stream_events_, this);
+ uint8_t buffer[2048] = {};
+
+ spa_pod_builder builder = spa_pod_builder{buffer, sizeof(buffer)};
+
+ std::vector<const spa_pod*> params;
+
+ spa_rectangle resolution =
+ SPA_RECTANGLE(uint32_t(width_), uint32_t(height_));
+ params.push_back(BuildFormat(&builder, SPA_VIDEO_FORMAT_BGRx,
+ /*modifiers=*/{}, &resolution));
+
+ auto flags =
+ pw_stream_flags(PW_STREAM_FLAG_DRIVER | PW_STREAM_FLAG_ALLOC_BUFFERS);
+ if (pw_stream_connect(pw_stream_, PW_DIRECTION_OUTPUT, SPA_ID_INVALID,
+ flags, params.data(), params.size()) != 0) {
+ RTC_LOG(LS_ERROR) << "PipeWire test: Could not connect receiving stream.";
+ pw_stream_destroy(pw_stream_);
+ pw_stream_ = nullptr;
+ return;
+ }
+ }
+
+ return;
+}
+
+TestScreenCastStreamProvider::~TestScreenCastStreamProvider() {
+ if (pw_main_loop_) {
+ pw_thread_loop_stop(pw_main_loop_);
+ }
+
+ if (pw_stream_) {
+ pw_stream_destroy(pw_stream_);
+ }
+
+ if (pw_core_) {
+ pw_core_disconnect(pw_core_);
+ }
+
+ if (pw_context_) {
+ pw_context_destroy(pw_context_);
+ }
+
+ if (pw_main_loop_) {
+ pw_thread_loop_destroy(pw_main_loop_);
+ }
+}
+
+void TestScreenCastStreamProvider::RecordFrame(RgbaColor rgba_color) {
+ const char* error;
+ if (pw_stream_get_state(pw_stream_, &error) != PW_STREAM_STATE_STREAMING) {
+ if (error) {
+ RTC_LOG(LS_ERROR)
+ << "PipeWire test: Failed to record frame: stream is not active: "
+ << error;
+ }
+ }
+
+ struct pw_buffer* buffer = pw_stream_dequeue_buffer(pw_stream_);
+ if (!buffer) {
+ RTC_LOG(LS_ERROR) << "PipeWire test: No available buffer";
+ return;
+ }
+
+ struct spa_buffer* spa_buffer = buffer->buffer;
+ struct spa_data* spa_data = spa_buffer->datas;
+ uint8_t* data = static_cast<uint8_t*>(spa_data->data);
+ if (!data) {
+ RTC_LOG(LS_ERROR)
+ << "PipeWire test: Failed to record frame: invalid buffer data";
+ pw_stream_queue_buffer(pw_stream_, buffer);
+ return;
+ }
+
+ const int stride = SPA_ROUND_UP_N(width_ * kBytesPerPixel, 4);
+
+ spa_data->chunk->offset = 0;
+ spa_data->chunk->size = height_ * stride;
+ spa_data->chunk->stride = stride;
+
+ uint32_t color = rgba_color.ToUInt32();
+ for (uint32_t i = 0; i < height_; i++) {
+ uint32_t* column = reinterpret_cast<uint32_t*>(data);
+ for (uint32_t j = 0; j < width_; j++) {
+ column[j] = color;
+ }
+ data += stride;
+ }
+
+ pw_stream_queue_buffer(pw_stream_, buffer);
+ if (observer_) {
+ observer_->OnFrameRecorded();
+ }
+}
+
+void TestScreenCastStreamProvider::StartStreaming() {
+ if (pw_stream_ && pw_node_id_ != 0) {
+ pw_stream_set_active(pw_stream_, true);
+ }
+}
+
+void TestScreenCastStreamProvider::StopStreaming() {
+ if (pw_stream_ && pw_node_id_ != 0) {
+ pw_stream_set_active(pw_stream_, false);
+ }
+}
+
+// static
+void TestScreenCastStreamProvider::OnCoreError(void* data,
+ uint32_t id,
+ int seq,
+ int res,
+ const char* message) {
+ TestScreenCastStreamProvider* that =
+ static_cast<TestScreenCastStreamProvider*>(data);
+ RTC_DCHECK(that);
+
+ RTC_LOG(LS_ERROR) << "PipeWire test: PipeWire remote error: " << message;
+}
+
+// static
+void TestScreenCastStreamProvider::OnStreamStateChanged(
+ void* data,
+ pw_stream_state old_state,
+ pw_stream_state state,
+ const char* error_message) {
+ TestScreenCastStreamProvider* that =
+ static_cast<TestScreenCastStreamProvider*>(data);
+ RTC_DCHECK(that);
+
+ switch (state) {
+ case PW_STREAM_STATE_ERROR:
+ RTC_LOG(LS_ERROR) << "PipeWire test: PipeWire stream state error: "
+ << error_message;
+ break;
+ case PW_STREAM_STATE_PAUSED:
+ if (that->pw_node_id_ == 0 && that->pw_stream_) {
+ that->pw_node_id_ = pw_stream_get_node_id(that->pw_stream_);
+ that->observer_->OnStreamReady(that->pw_node_id_);
+ } else {
+ // Stop streaming
+ that->is_streaming_ = false;
+ that->observer_->OnStopStreaming();
+ }
+ break;
+ case PW_STREAM_STATE_STREAMING:
+ // Start streaming
+ that->is_streaming_ = true;
+ that->observer_->OnStartStreaming();
+ break;
+ case PW_STREAM_STATE_CONNECTING:
+ break;
+ case PW_STREAM_STATE_UNCONNECTED:
+ if (that->is_streaming_) {
+ // Stop streaming
+ that->is_streaming_ = false;
+ that->observer_->OnStopStreaming();
+ }
+ break;
+ }
+}
+
+// static
+void TestScreenCastStreamProvider::OnStreamParamChanged(
+ void* data,
+ uint32_t id,
+ const struct spa_pod* format) {
+ TestScreenCastStreamProvider* that =
+ static_cast<TestScreenCastStreamProvider*>(data);
+ RTC_DCHECK(that);
+
+ RTC_LOG(LS_INFO) << "PipeWire test: PipeWire stream format changed.";
+ if (!format || id != SPA_PARAM_Format) {
+ return;
+ }
+
+ spa_format_video_raw_parse(format, &that->spa_video_format_);
+
+ auto stride = SPA_ROUND_UP_N(that->width_ * kBytesPerPixel, 4);
+
+ uint8_t buffer[1024] = {};
+ auto builder = spa_pod_builder{buffer, sizeof(buffer)};
+
+ // Setup buffers and meta header for new format.
+
+ std::vector<const spa_pod*> params;
+ const int buffer_types = (1 << SPA_DATA_MemFd);
+ spa_rectangle resolution = SPA_RECTANGLE(that->width_, that->height_);
+
+ params.push_back(reinterpret_cast<spa_pod*>(spa_pod_builder_add_object(
+ &builder, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers,
+ SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&resolution),
+ SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(16, 2, 16),
+ SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), SPA_PARAM_BUFFERS_stride,
+ SPA_POD_Int(stride), SPA_PARAM_BUFFERS_size,
+ SPA_POD_Int(stride * that->height_), SPA_PARAM_BUFFERS_align,
+ SPA_POD_Int(16), SPA_PARAM_BUFFERS_dataType,
+ SPA_POD_CHOICE_FLAGS_Int(buffer_types))));
+ params.push_back(reinterpret_cast<spa_pod*>(spa_pod_builder_add_object(
+ &builder, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type,
+ SPA_POD_Id(SPA_META_Header), SPA_PARAM_META_size,
+ SPA_POD_Int(sizeof(struct spa_meta_header)))));
+
+ pw_stream_update_params(that->pw_stream_, params.data(), params.size());
+}
+
+// static
+void TestScreenCastStreamProvider::OnStreamAddBuffer(void* data,
+ pw_buffer* buffer) {
+ TestScreenCastStreamProvider* that =
+ static_cast<TestScreenCastStreamProvider*>(data);
+ RTC_DCHECK(that);
+
+ struct spa_data* spa_data = buffer->buffer->datas;
+
+ spa_data->mapoffset = 0;
+ spa_data->flags = SPA_DATA_FLAG_READWRITE;
+
+ if (!(spa_data[0].type & (1 << SPA_DATA_MemFd))) {
+ RTC_LOG(LS_ERROR)
+ << "PipeWire test: Client doesn't support memfd buffer data type";
+ return;
+ }
+
+ const int stride = SPA_ROUND_UP_N(that->width_ * kBytesPerPixel, 4);
+ spa_data->maxsize = stride * that->height_;
+ spa_data->type = SPA_DATA_MemFd;
+ spa_data->fd =
+ memfd_create("pipewire-test-memfd", MFD_CLOEXEC | MFD_ALLOW_SEALING);
+ if (spa_data->fd == -1) {
+ RTC_LOG(LS_ERROR) << "PipeWire test: Can't create memfd";
+ return;
+ }
+
+ spa_data->mapoffset = 0;
+
+ if (ftruncate(spa_data->fd, spa_data->maxsize) < 0) {
+ RTC_LOG(LS_ERROR) << "PipeWire test: Can't truncate to"
+ << spa_data->maxsize;
+ return;
+ }
+
+ unsigned int seals = F_SEAL_GROW | F_SEAL_SHRINK | F_SEAL_SEAL;
+ if (fcntl(spa_data->fd, F_ADD_SEALS, seals) == -1) {
+ RTC_LOG(LS_ERROR) << "PipeWire test: Failed to add seals";
+ }
+
+ spa_data->data = mmap(nullptr, spa_data->maxsize, PROT_READ | PROT_WRITE,
+ MAP_SHARED, spa_data->fd, spa_data->mapoffset);
+ if (spa_data->data == MAP_FAILED) {
+ RTC_LOG(LS_ERROR) << "PipeWire test: Failed to mmap memory";
+ } else {
+ that->observer_->OnBufferAdded();
+ RTC_LOG(LS_INFO) << "PipeWire test: Memfd created successfully: "
+ << spa_data->data << spa_data->maxsize;
+ }
+}
+
+// static
+void TestScreenCastStreamProvider::OnStreamRemoveBuffer(void* data,
+ pw_buffer* buffer) {
+ TestScreenCastStreamProvider* that =
+ static_cast<TestScreenCastStreamProvider*>(data);
+ RTC_DCHECK(that);
+
+ struct spa_buffer* spa_buffer = buffer->buffer;
+ struct spa_data* spa_data = spa_buffer->datas;
+ if (spa_data && spa_data->type == SPA_DATA_MemFd) {
+ munmap(spa_data->data, spa_data->maxsize);
+ close(spa_data->fd);
+ }
+}
+
+uint32_t TestScreenCastStreamProvider::PipeWireNodeId() {
+ return pw_node_id_;
+}
+
+} // namespace webrtc
diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/wayland/test/test_screencast_stream_provider.h b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/test/test_screencast_stream_provider.h
new file mode 100644
index 0000000000..d893aa63ab
--- /dev/null
+++ b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/test/test_screencast_stream_provider.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2022 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_TEST_TEST_SCREENCAST_STREAM_PROVIDER_H_
+#define MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_TEST_TEST_SCREENCAST_STREAM_PROVIDER_H_
+
+#include <pipewire/pipewire.h>
+#include <spa/param/video/format-utils.h>
+
+#include "modules/desktop_capture/linux/wayland/screencast_stream_utils.h"
+#include "modules/desktop_capture/rgba_color.h"
+#include "rtc_base/random.h"
+
+namespace webrtc {
+
+class TestScreenCastStreamProvider {
+ public:
+ class Observer {
+ public:
+ virtual void OnBufferAdded() = 0;
+ virtual void OnFrameRecorded() = 0;
+ virtual void OnStreamReady(uint32_t stream_node_id) = 0;
+ virtual void OnStartStreaming() = 0;
+ virtual void OnStopStreaming() = 0;
+
+ protected:
+ Observer() = default;
+ virtual ~Observer() = default;
+ };
+
+ explicit TestScreenCastStreamProvider(Observer* observer,
+ uint32_t width,
+ uint32_t height);
+ ~TestScreenCastStreamProvider();
+
+ uint32_t PipeWireNodeId();
+
+ void RecordFrame(RgbaColor rgba_color);
+ void StartStreaming();
+ void StopStreaming();
+
+ private:
+ Observer* observer_;
+
+ // Resolution parameters.
+ uint32_t width_ = 0;
+ uint32_t height_ = 0;
+
+ bool is_streaming_ = false;
+ uint32_t pw_node_id_ = 0;
+
+ // PipeWire types
+ struct pw_context* pw_context_ = nullptr;
+ struct pw_core* pw_core_ = nullptr;
+ struct pw_stream* pw_stream_ = nullptr;
+ struct pw_thread_loop* pw_main_loop_ = nullptr;
+
+ spa_hook spa_core_listener_;
+ spa_hook spa_stream_listener_;
+
+ // event handlers
+ pw_core_events pw_core_events_ = {};
+ pw_stream_events pw_stream_events_ = {};
+
+ struct spa_video_info_raw spa_video_format_;
+
+ // PipeWire callbacks
+ static void OnCoreError(void* data,
+ uint32_t id,
+ int seq,
+ int res,
+ const char* message);
+ static void OnStreamAddBuffer(void* data, pw_buffer* buffer);
+ static void OnStreamRemoveBuffer(void* data, pw_buffer* buffer);
+ static void OnStreamParamChanged(void* data,
+ uint32_t id,
+ const struct spa_pod* format);
+ static void OnStreamStateChanged(void* data,
+ pw_stream_state old_state,
+ pw_stream_state state,
+ const char* error_message);
+};
+
+} // namespace webrtc
+
+#endif // MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_TEST_TEST_SCREENCAST_STREAM_PROVIDER_H_
diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/wayland/xdg_desktop_portal_utils.h b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/xdg_desktop_portal_utils.h
new file mode 100644
index 0000000000..b213e50308
--- /dev/null
+++ b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/xdg_desktop_portal_utils.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2022 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_XDG_DESKTOP_PORTAL_UTILS_H_
+#define MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_XDG_DESKTOP_PORTAL_UTILS_H_
+
+// TODO(bugs.webrtc.org/14187): remove when all users are gone
+#include "modules/portal/xdg_desktop_portal_utils.h"
+
+#endif // MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_XDG_DESKTOP_PORTAL_UTILS_H_
diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/wayland/xdg_session_details.h b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/xdg_session_details.h
new file mode 100644
index 0000000000..9feff5bdf7
--- /dev/null
+++ b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/xdg_session_details.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2022 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_XDG_SESSION_DETAILS_H_
+#define MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_XDG_SESSION_DETAILS_H_
+
+// TODO(bugs.webrtc.org/14187): remove when all users are gone
+#include "modules/portal/xdg_session_details.h"
+
+#endif // MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_XDG_SESSION_DETAILS_H_
diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/x11/mouse_cursor_monitor_x11.cc b/third_party/libwebrtc/modules/desktop_capture/linux/x11/mouse_cursor_monitor_x11.cc
new file mode 100644
index 0000000000..d4b85af6bd
--- /dev/null
+++ b/third_party/libwebrtc/modules/desktop_capture/linux/x11/mouse_cursor_monitor_x11.cc
@@ -0,0 +1,258 @@
+/*
+ * Copyright (c) 2013 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/x11/mouse_cursor_monitor_x11.h"
+
+#include <X11/Xlib.h>
+#include <X11/extensions/Xfixes.h>
+#include <X11/extensions/xfixeswire.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include <algorithm>
+#include <memory>
+
+#include "modules/desktop_capture/desktop_capture_options.h"
+#include "modules/desktop_capture/desktop_capture_types.h"
+#include "modules/desktop_capture/desktop_frame.h"
+#include "modules/desktop_capture/desktop_geometry.h"
+#include "modules/desktop_capture/linux/x11/x_error_trap.h"
+#include "modules/desktop_capture/mouse_cursor.h"
+#include "modules/desktop_capture/mouse_cursor_monitor.h"
+#include "rtc_base/checks.h"
+#include "rtc_base/logging.h"
+
+namespace {
+
+// WindowCapturer returns window IDs of X11 windows with WM_STATE attribute.
+// These windows may not be immediate children of the root window, because
+// window managers may re-parent them to add decorations. However,
+// XQueryPointer() expects to be passed children of the root. This function
+// searches up the list of the windows to find the root child that corresponds
+// to `window`.
+Window GetTopLevelWindow(Display* display, Window window) {
+ webrtc::XErrorTrap error_trap(display);
+ while (true) {
+ // If the window is in WithdrawnState then look at all of its children.
+ ::Window root, parent;
+ ::Window* children;
+ unsigned int num_children;
+ if (!XQueryTree(display, window, &root, &parent, &children,
+ &num_children)) {
+ RTC_LOG(LS_ERROR) << "Failed to query for child windows although window"
+ "does not have a valid WM_STATE.";
+ return None;
+ }
+ if (children)
+ XFree(children);
+
+ if (parent == root)
+ break;
+
+ window = parent;
+ }
+
+ return window;
+}
+
+} // namespace
+
+namespace webrtc {
+
+MouseCursorMonitorX11::MouseCursorMonitorX11(
+ const DesktopCaptureOptions& options,
+ Window window)
+ : x_display_(options.x_display()),
+ callback_(NULL),
+ mode_(SHAPE_AND_POSITION),
+ window_(window),
+ have_xfixes_(false),
+ xfixes_event_base_(-1),
+ xfixes_error_base_(-1) {
+ // Set a default initial cursor shape in case XFixes is not present.
+ const int kSize = 5;
+ std::unique_ptr<DesktopFrame> default_cursor(
+ new BasicDesktopFrame(DesktopSize(kSize, kSize)));
+ const uint8_t pixels[kSize * kSize] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff,
+ 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
+ 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ uint8_t* ptr = default_cursor->data();
+ for (int y = 0; y < kSize; ++y) {
+ for (int x = 0; x < kSize; ++x) {
+ *ptr++ = pixels[kSize * y + x];
+ *ptr++ = pixels[kSize * y + x];
+ *ptr++ = pixels[kSize * y + x];
+ *ptr++ = 0xff;
+ }
+ }
+ DesktopVector hotspot(2, 2);
+ cursor_shape_.reset(new MouseCursor(default_cursor.release(), hotspot));
+}
+
+MouseCursorMonitorX11::~MouseCursorMonitorX11() {
+ if (have_xfixes_) {
+ x_display_->RemoveEventHandler(xfixes_event_base_ + XFixesCursorNotify,
+ this);
+ }
+}
+
+void MouseCursorMonitorX11::Init(Callback* callback, Mode mode) {
+ // Init can be called only if not started
+ RTC_DCHECK(!callback_);
+ RTC_DCHECK(callback);
+
+ callback_ = callback;
+ mode_ = mode;
+
+ have_xfixes_ =
+ XFixesQueryExtension(display(), &xfixes_event_base_, &xfixes_error_base_);
+
+ if (have_xfixes_) {
+ // Register for changes to the cursor shape.
+ XErrorTrap error_trap(display());
+ XFixesSelectCursorInput(display(), window_, XFixesDisplayCursorNotifyMask);
+ x_display_->AddEventHandler(xfixes_event_base_ + XFixesCursorNotify, this);
+
+ CaptureCursor();
+ } else {
+ RTC_LOG(LS_INFO) << "X server does not support XFixes.";
+ }
+}
+
+void MouseCursorMonitorX11::Capture() {
+ RTC_DCHECK(callback_);
+
+ // Process X11 events in case XFixes has sent cursor notification.
+ x_display_->ProcessPendingXEvents();
+
+ // cursor_shape_| is set only if we were notified of a cursor shape change.
+ if (cursor_shape_.get())
+ callback_->OnMouseCursor(cursor_shape_.release());
+
+ // Get cursor position if necessary.
+ if (mode_ == SHAPE_AND_POSITION) {
+ int root_x;
+ int root_y;
+ int win_x;
+ int win_y;
+ Window root_window;
+ Window child_window;
+ unsigned int mask;
+
+ CursorState state;
+ {
+ XErrorTrap error_trap(display());
+ Bool result =
+ XQueryPointer(display(), window_, &root_window, &child_window,
+ &root_x, &root_y, &win_x, &win_y, &mask);
+ if (!result || error_trap.GetLastErrorAndDisable() != 0) {
+ state = OUTSIDE;
+ } else {
+ // In screen mode (window_ == root_window) the mouse is always inside.
+ // XQueryPointer() sets `child_window` to None if the cursor is outside
+ // `window_`.
+ state =
+ (window_ == root_window || child_window != None) ? INSIDE : OUTSIDE;
+ }
+ }
+
+ // As the comments to GetTopLevelWindow() above indicate, in window capture,
+ // the cursor position capture happens in `window_`, while the frame catpure
+ // happens in `child_window`. These two windows are not alwyas same, as
+ // window manager may add some decorations to the `window_`. So translate
+ // the coordinate in `window_` to the coordinate space of `child_window`.
+ if (window_ != root_window && state == INSIDE) {
+ int translated_x, translated_y;
+ Window unused;
+ if (XTranslateCoordinates(display(), window_, child_window, win_x, win_y,
+ &translated_x, &translated_y, &unused)) {
+ win_x = translated_x;
+ win_y = translated_y;
+ }
+ }
+
+ // X11 always starts the coordinate from (0, 0), so we do not need to
+ // translate here.
+ callback_->OnMouseCursorPosition(DesktopVector(root_x, root_y));
+ }
+}
+
+bool MouseCursorMonitorX11::HandleXEvent(const XEvent& event) {
+ if (have_xfixes_ && event.type == xfixes_event_base_ + XFixesCursorNotify) {
+ const XFixesCursorNotifyEvent* cursor_event =
+ reinterpret_cast<const XFixesCursorNotifyEvent*>(&event);
+ if (cursor_event->subtype == XFixesDisplayCursorNotify) {
+ CaptureCursor();
+ }
+ // Return false, even if the event has been handled, because there might be
+ // other listeners for cursor notifications.
+ }
+ return false;
+}
+
+void MouseCursorMonitorX11::CaptureCursor() {
+ RTC_DCHECK(have_xfixes_);
+
+ XFixesCursorImage* img;
+ {
+ XErrorTrap error_trap(display());
+ img = XFixesGetCursorImage(display());
+ if (!img || error_trap.GetLastErrorAndDisable() != 0)
+ return;
+ }
+
+ std::unique_ptr<DesktopFrame> image(
+ new BasicDesktopFrame(DesktopSize(img->width, img->height)));
+
+ // Xlib stores 32-bit data in longs, even if longs are 64-bits long.
+ unsigned long* src = img->pixels; // NOLINT(runtime/int)
+ uint32_t* dst = reinterpret_cast<uint32_t*>(image->data());
+ uint32_t* dst_end = dst + (img->width * img->height);
+ while (dst < dst_end) {
+ *dst++ = static_cast<uint32_t>(*src++);
+ }
+
+ DesktopVector hotspot(std::min(img->width, img->xhot),
+ std::min(img->height, img->yhot));
+
+ XFree(img);
+
+ cursor_shape_.reset(new MouseCursor(image.release(), hotspot));
+}
+
+// static
+MouseCursorMonitor* MouseCursorMonitorX11::CreateForWindow(
+ const DesktopCaptureOptions& options,
+ WindowId window) {
+ if (!options.x_display())
+ return NULL;
+ window = GetTopLevelWindow(options.x_display()->display(), window);
+ if (window == None)
+ return NULL;
+ return new MouseCursorMonitorX11(options, window);
+}
+
+MouseCursorMonitor* MouseCursorMonitorX11::CreateForScreen(
+ const DesktopCaptureOptions& options,
+ ScreenId screen) {
+ if (!options.x_display())
+ return NULL;
+ return new MouseCursorMonitorX11(
+ options, DefaultRootWindow(options.x_display()->display()));
+}
+
+std::unique_ptr<MouseCursorMonitor> MouseCursorMonitorX11::Create(
+ const DesktopCaptureOptions& options) {
+ return std::unique_ptr<MouseCursorMonitor>(
+ CreateForScreen(options, kFullDesktopScreenId));
+}
+
+} // namespace webrtc
diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/x11/mouse_cursor_monitor_x11.h b/third_party/libwebrtc/modules/desktop_capture/linux/x11/mouse_cursor_monitor_x11.h
new file mode 100644
index 0000000000..980d254a0a
--- /dev/null
+++ b/third_party/libwebrtc/modules/desktop_capture/linux/x11/mouse_cursor_monitor_x11.h
@@ -0,0 +1,68 @@
+/*
+ * 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_X11_MOUSE_CURSOR_MONITOR_X11_H_
+#define MODULES_DESKTOP_CAPTURE_LINUX_X11_MOUSE_CURSOR_MONITOR_X11_H_
+
+#include <X11/X.h>
+
+#include <memory>
+
+#include "api/scoped_refptr.h"
+#include "modules/desktop_capture/desktop_capture_options.h"
+#include "modules/desktop_capture/desktop_capture_types.h"
+#include "modules/desktop_capture/linux/x11/shared_x_display.h"
+#include "modules/desktop_capture/mouse_cursor.h"
+#include "modules/desktop_capture/mouse_cursor_monitor.h"
+
+namespace webrtc {
+
+class MouseCursorMonitorX11 : public MouseCursorMonitor,
+ public SharedXDisplay::XEventHandler {
+ public:
+ MouseCursorMonitorX11(const DesktopCaptureOptions& options, Window window);
+ ~MouseCursorMonitorX11() override;
+
+ static MouseCursorMonitor* CreateForWindow(
+ const DesktopCaptureOptions& options,
+ WindowId window);
+ static MouseCursorMonitor* CreateForScreen(
+ const DesktopCaptureOptions& options,
+ ScreenId screen);
+ static std::unique_ptr<MouseCursorMonitor> Create(
+ const DesktopCaptureOptions& options);
+
+ void Init(Callback* callback, Mode mode) override;
+ void Capture() override;
+
+ private:
+ // SharedXDisplay::XEventHandler interface.
+ bool HandleXEvent(const XEvent& event) override;
+
+ Display* display() { return x_display_->display(); }
+
+ // Captures current cursor shape and stores it in `cursor_shape_`.
+ void CaptureCursor();
+
+ rtc::scoped_refptr<SharedXDisplay> x_display_;
+ Callback* callback_;
+ Mode mode_;
+ Window window_;
+
+ bool have_xfixes_;
+ int xfixes_event_base_;
+ int xfixes_error_base_;
+
+ std::unique_ptr<MouseCursor> cursor_shape_;
+};
+
+} // namespace webrtc
+
+#endif // MODULES_DESKTOP_CAPTURE_LINUX_X11_MOUSE_CURSOR_MONITOR_X11_H_
diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/x11/screen_capturer_x11.cc b/third_party/libwebrtc/modules/desktop_capture/linux/x11/screen_capturer_x11.cc
new file mode 100644
index 0000000000..fa6334e8ba
--- /dev/null
+++ b/third_party/libwebrtc/modules/desktop_capture/linux/x11/screen_capturer_x11.cc
@@ -0,0 +1,517 @@
+/*
+ * Copyright (c) 2013 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/x11/screen_capturer_x11.h"
+
+#include <X11/Xlib.h>
+#include <X11/extensions/Xdamage.h>
+#include <X11/extensions/Xfixes.h>
+#include <X11/extensions/damagewire.h>
+#include <dlfcn.h>
+#include <stdint.h>
+#include <string.h>
+
+#include <memory>
+#include <utility>
+
+#include "modules/desktop_capture/desktop_capture_options.h"
+#include "modules/desktop_capture/desktop_capturer.h"
+#include "modules/desktop_capture/desktop_frame.h"
+#include "modules/desktop_capture/desktop_geometry.h"
+#include "modules/desktop_capture/linux/x11/x_server_pixel_buffer.h"
+#include "modules/desktop_capture/screen_capture_frame_queue.h"
+#include "modules/desktop_capture/screen_capturer_helper.h"
+#include "modules/desktop_capture/shared_desktop_frame.h"
+#include "rtc_base/checks.h"
+#include "rtc_base/logging.h"
+#include "rtc_base/sanitizer.h"
+#include "rtc_base/time_utils.h"
+#include "rtc_base/trace_event.h"
+
+namespace webrtc {
+
+ScreenCapturerX11::ScreenCapturerX11() {
+ helper_.SetLogGridSize(4);
+}
+
+ScreenCapturerX11::~ScreenCapturerX11() {
+ options_.x_display()->RemoveEventHandler(ConfigureNotify, this);
+ if (use_damage_) {
+ options_.x_display()->RemoveEventHandler(damage_event_base_ + XDamageNotify,
+ this);
+ }
+ if (use_randr_) {
+ options_.x_display()->RemoveEventHandler(
+ randr_event_base_ + RRScreenChangeNotify, this);
+ }
+ DeinitXlib();
+}
+
+bool ScreenCapturerX11::Init(const DesktopCaptureOptions& options) {
+ TRACE_EVENT0("webrtc", "ScreenCapturerX11::Init");
+ options_ = options;
+
+ atom_cache_ = std::make_unique<XAtomCache>(display());
+
+ root_window_ = RootWindow(display(), DefaultScreen(display()));
+ if (root_window_ == BadValue) {
+ RTC_LOG(LS_ERROR) << "Unable to get the root window";
+ DeinitXlib();
+ return false;
+ }
+
+ gc_ = XCreateGC(display(), root_window_, 0, NULL);
+ if (gc_ == NULL) {
+ RTC_LOG(LS_ERROR) << "Unable to get graphics context";
+ DeinitXlib();
+ return false;
+ }
+
+ options_.x_display()->AddEventHandler(ConfigureNotify, this);
+
+ // Check for XFixes extension. This is required for cursor shape
+ // notifications, and for our use of XDamage.
+ if (XFixesQueryExtension(display(), &xfixes_event_base_,
+ &xfixes_error_base_)) {
+ has_xfixes_ = true;
+ } else {
+ RTC_LOG(LS_INFO) << "X server does not support XFixes.";
+ }
+
+ // Register for changes to the dimensions of the root window.
+ XSelectInput(display(), root_window_, StructureNotifyMask);
+
+ if (!x_server_pixel_buffer_.Init(atom_cache_.get(),
+ DefaultRootWindow(display()))) {
+ RTC_LOG(LS_ERROR) << "Failed to initialize pixel buffer.";
+ return false;
+ }
+
+ if (options_.use_update_notifications()) {
+ InitXDamage();
+ }
+
+ InitXrandr();
+
+ // Default source set here so that selected_monitor_rect_ is sized correctly.
+ SelectSource(kFullDesktopScreenId);
+
+ return true;
+}
+
+void ScreenCapturerX11::InitXDamage() {
+ // Our use of XDamage requires XFixes.
+ if (!has_xfixes_) {
+ return;
+ }
+
+ // Check for XDamage extension.
+ if (!XDamageQueryExtension(display(), &damage_event_base_,
+ &damage_error_base_)) {
+ RTC_LOG(LS_INFO) << "X server does not support XDamage.";
+ return;
+ }
+
+ // TODO(lambroslambrou): Disable DAMAGE in situations where it is known
+ // to fail, such as when Desktop Effects are enabled, with graphics
+ // drivers (nVidia, ATI) that fail to report DAMAGE notifications
+ // properly.
+
+ // Request notifications every time the screen becomes damaged.
+ damage_handle_ =
+ XDamageCreate(display(), root_window_, XDamageReportNonEmpty);
+ if (!damage_handle_) {
+ RTC_LOG(LS_ERROR) << "Unable to initialize XDamage.";
+ return;
+ }
+
+ // Create an XFixes server-side region to collate damage into.
+ damage_region_ = XFixesCreateRegion(display(), 0, 0);
+ if (!damage_region_) {
+ XDamageDestroy(display(), damage_handle_);
+ RTC_LOG(LS_ERROR) << "Unable to create XFixes region.";
+ return;
+ }
+
+ options_.x_display()->AddEventHandler(damage_event_base_ + XDamageNotify,
+ this);
+
+ use_damage_ = true;
+ RTC_LOG(LS_INFO) << "Using XDamage extension.";
+}
+
+RTC_NO_SANITIZE("cfi-icall")
+void ScreenCapturerX11::InitXrandr() {
+ int major_version = 0;
+ int minor_version = 0;
+ int error_base_ignored = 0;
+ if (XRRQueryExtension(display(), &randr_event_base_, &error_base_ignored) &&
+ XRRQueryVersion(display(), &major_version, &minor_version)) {
+ if (major_version > 1 || (major_version == 1 && minor_version >= 5)) {
+ // Dynamically link XRRGetMonitors and XRRFreeMonitors as a workaround
+ // to avoid a dependency issue with Debian 8.
+ get_monitors_ = reinterpret_cast<get_monitors_func>(
+ dlsym(RTLD_DEFAULT, "XRRGetMonitors"));
+ free_monitors_ = reinterpret_cast<free_monitors_func>(
+ dlsym(RTLD_DEFAULT, "XRRFreeMonitors"));
+ if (get_monitors_ && free_monitors_) {
+ use_randr_ = true;
+ RTC_LOG(LS_INFO) << "Using XRandR extension v" << major_version << '.'
+ << minor_version << '.';
+ monitors_ =
+ get_monitors_(display(), root_window_, true, &num_monitors_);
+
+ // Register for screen change notifications
+ XRRSelectInput(display(), root_window_, RRScreenChangeNotifyMask);
+ options_.x_display()->AddEventHandler(
+ randr_event_base_ + RRScreenChangeNotify, this);
+ } else {
+ RTC_LOG(LS_ERROR) << "Unable to link XRandR monitor functions.";
+ }
+ } else {
+ RTC_LOG(LS_ERROR) << "XRandR entension is older than v1.5.";
+ }
+ } else {
+ RTC_LOG(LS_ERROR) << "X server does not support XRandR.";
+ }
+}
+
+RTC_NO_SANITIZE("cfi-icall")
+void ScreenCapturerX11::UpdateMonitors() {
+ // The queue should be reset whenever |selected_monitor_rect_| changes, so
+ // that the DCHECKs in CaptureScreen() are satisfied.
+ queue_.Reset();
+
+ if (monitors_) {
+ free_monitors_(monitors_);
+ monitors_ = nullptr;
+ }
+
+ monitors_ = get_monitors_(display(), root_window_, true, &num_monitors_);
+
+ if (selected_monitor_name_) {
+ if (selected_monitor_name_ == static_cast<Atom>(kFullDesktopScreenId)) {
+ selected_monitor_rect_ =
+ DesktopRect::MakeSize(x_server_pixel_buffer_.window_size());
+ return;
+ }
+
+ for (int i = 0; i < num_monitors_; ++i) {
+ XRRMonitorInfo& m = monitors_[i];
+ if (selected_monitor_name_ == m.name) {
+ RTC_LOG(LS_INFO) << "XRandR monitor " << m.name << " rect updated.";
+ selected_monitor_rect_ =
+ DesktopRect::MakeXYWH(m.x, m.y, m.width, m.height);
+ const auto& pixel_buffer_rect = x_server_pixel_buffer_.window_rect();
+ if (!pixel_buffer_rect.ContainsRect(selected_monitor_rect_)) {
+ // This is never expected to happen, but crop the rectangle anyway
+ // just in case the server returns inconsistent information.
+ // CaptureScreen() expects `selected_monitor_rect_` to lie within
+ // the pixel-buffer's rectangle.
+ RTC_LOG(LS_WARNING)
+ << "Cropping selected monitor rect to fit the pixel-buffer.";
+ selected_monitor_rect_.IntersectWith(pixel_buffer_rect);
+ }
+ return;
+ }
+ }
+
+ // The selected monitor is not connected anymore
+ RTC_LOG(LS_INFO) << "XRandR selected monitor " << selected_monitor_name_
+ << " lost.";
+ selected_monitor_rect_ = DesktopRect::MakeWH(0, 0);
+ }
+}
+
+void ScreenCapturerX11::Start(Callback* callback) {
+ RTC_DCHECK(!callback_);
+ RTC_DCHECK(callback);
+
+ callback_ = callback;
+}
+
+void ScreenCapturerX11::CaptureFrame() {
+ TRACE_EVENT0("webrtc", "ScreenCapturerX11::CaptureFrame");
+ int64_t capture_start_time_nanos = rtc::TimeNanos();
+
+ queue_.MoveToNextFrame();
+ if (queue_.current_frame() && queue_.current_frame()->IsShared()) {
+ RTC_DLOG(LS_WARNING) << "Overwriting frame that is still shared.";
+ }
+
+ // Process XEvents for XDamage and cursor shape tracking.
+ options_.x_display()->ProcessPendingXEvents();
+
+ // ProcessPendingXEvents() may call ScreenConfigurationChanged() which
+ // reinitializes `x_server_pixel_buffer_`. Check if the pixel buffer is still
+ // in a good shape.
+ if (!x_server_pixel_buffer_.is_initialized()) {
+ // We failed to initialize pixel buffer.
+ RTC_LOG(LS_ERROR) << "Pixel buffer is not initialized.";
+ callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr);
+ return;
+ }
+
+ // Allocate the current frame buffer only if it is not already allocated.
+ // Note that we can't reallocate other buffers at this point, since the caller
+ // may still be reading from them.
+ if (!queue_.current_frame()) {
+ std::unique_ptr<DesktopFrame> frame(
+ new BasicDesktopFrame(selected_monitor_rect_.size()));
+
+ // We set the top-left of the frame so the mouse cursor will be composited
+ // properly, and our frame buffer will not be overrun while blitting.
+ frame->set_top_left(selected_monitor_rect_.top_left());
+ queue_.ReplaceCurrentFrame(SharedDesktopFrame::Wrap(std::move(frame)));
+ }
+
+ std::unique_ptr<DesktopFrame> result = CaptureScreen();
+ if (!result) {
+ RTC_LOG(LS_WARNING) << "Temporarily failed to capture screen.";
+ callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr);
+ return;
+ }
+
+ last_invalid_region_ = result->updated_region();
+ result->set_capture_time_ms((rtc::TimeNanos() - capture_start_time_nanos) /
+ rtc::kNumNanosecsPerMillisec);
+ result->set_capturer_id(DesktopCapturerId::kX11CapturerLinux);
+ callback_->OnCaptureResult(Result::SUCCESS, std::move(result));
+}
+
+bool ScreenCapturerX11::GetSourceList(SourceList* sources) {
+ RTC_DCHECK(sources->size() == 0);
+ if (!use_randr_) {
+ sources->push_back({});
+ return true;
+ }
+
+ // Ensure that `monitors_` is updated with changes that may have happened
+ // between calls to GetSourceList().
+ options_.x_display()->ProcessPendingXEvents();
+
+ for (int i = 0; i < num_monitors_; ++i) {
+ XRRMonitorInfo& m = monitors_[i];
+ char* monitor_title = XGetAtomName(display(), m.name);
+
+ // Note name is an X11 Atom used to id the monitor.
+ sources->push_back({static_cast<SourceId>(m.name), 0, monitor_title});
+ XFree(monitor_title);
+ }
+
+ return true;
+}
+
+bool ScreenCapturerX11::SelectSource(SourceId id) {
+ // Prevent the reuse of any frame buffers allocated for a previously selected
+ // source. This is required to stop crashes, or old data from appearing in
+ // a captured frame, when the new source is sized differently then the source
+ // that was selected at the time a reused frame buffer was created.
+ queue_.Reset();
+
+ if (!use_randr_ || id == kFullDesktopScreenId) {
+ selected_monitor_name_ = kFullDesktopScreenId;
+ selected_monitor_rect_ =
+ DesktopRect::MakeSize(x_server_pixel_buffer_.window_size());
+ return true;
+ }
+
+ for (int i = 0; i < num_monitors_; ++i) {
+ if (id == static_cast<SourceId>(monitors_[i].name)) {
+ RTC_LOG(LS_INFO) << "XRandR selected source: " << id;
+ XRRMonitorInfo& m = monitors_[i];
+ selected_monitor_name_ = m.name;
+ selected_monitor_rect_ =
+ DesktopRect::MakeXYWH(m.x, m.y, m.width, m.height);
+ const auto& pixel_buffer_rect = x_server_pixel_buffer_.window_rect();
+ if (!pixel_buffer_rect.ContainsRect(selected_monitor_rect_)) {
+ RTC_LOG(LS_WARNING)
+ << "Cropping selected monitor rect to fit the pixel-buffer.";
+ selected_monitor_rect_.IntersectWith(pixel_buffer_rect);
+ }
+ return true;
+ }
+ }
+ return false;
+}
+
+bool ScreenCapturerX11::HandleXEvent(const XEvent& event) {
+ if (use_damage_ && (event.type == damage_event_base_ + XDamageNotify)) {
+ const XDamageNotifyEvent* damage_event =
+ reinterpret_cast<const XDamageNotifyEvent*>(&event);
+ if (damage_event->damage != damage_handle_)
+ return false;
+ RTC_DCHECK(damage_event->level == XDamageReportNonEmpty);
+ return true;
+ } else if (use_randr_ &&
+ event.type == randr_event_base_ + RRScreenChangeNotify) {
+ XRRUpdateConfiguration(const_cast<XEvent*>(&event));
+ UpdateMonitors();
+ RTC_LOG(LS_INFO) << "XRandR screen change event received.";
+ return false;
+ } else if (event.type == ConfigureNotify) {
+ ScreenConfigurationChanged();
+ return false;
+ }
+ return false;
+}
+
+std::unique_ptr<DesktopFrame> ScreenCapturerX11::CaptureScreen() {
+ std::unique_ptr<SharedDesktopFrame> frame = queue_.current_frame()->Share();
+ RTC_DCHECK(selected_monitor_rect_.size().equals(frame->size()));
+ RTC_DCHECK(selected_monitor_rect_.top_left().equals(frame->top_left()));
+
+ // Pass the screen size to the helper, so it can clip the invalid region if it
+ // expands that region to a grid. Note that the helper operates in the
+ // DesktopFrame coordinate system where the top-left pixel is (0, 0), even for
+ // a monitor with non-zero offset relative to `x_server_pixel_buffer_`.
+ helper_.set_size_most_recent(frame->size());
+
+ // In the DAMAGE case, ensure the frame is up-to-date with the previous frame
+ // if any. If there isn't a previous frame, that means a screen-resolution
+ // change occurred, and `invalid_rects` will be updated to include the whole
+ // screen.
+ if (use_damage_ && queue_.previous_frame())
+ SynchronizeFrame();
+
+ DesktopRegion* updated_region = frame->mutable_updated_region();
+
+ x_server_pixel_buffer_.Synchronize();
+ if (use_damage_ && queue_.previous_frame()) {
+ // Atomically fetch and clear the damage region.
+ XDamageSubtract(display(), damage_handle_, None, damage_region_);
+ int rects_num = 0;
+ XRectangle bounds;
+ XRectangle* rects = XFixesFetchRegionAndBounds(display(), damage_region_,
+ &rects_num, &bounds);
+ for (int i = 0; i < rects_num; ++i) {
+ auto damage_rect = DesktopRect::MakeXYWH(rects[i].x, rects[i].y,
+ rects[i].width, rects[i].height);
+
+ // Damage-regions are relative to `x_server_pixel_buffer`, so convert the
+ // region to DesktopFrame coordinates where the top-left is always (0, 0),
+ // before adding to the frame's updated_region. `helper_` also operates in
+ // DesktopFrame coordinates, and it will take care of cropping away any
+ // damage-regions that lie outside the selected monitor.
+ damage_rect.Translate(-frame->top_left());
+ updated_region->AddRect(damage_rect);
+ }
+ XFree(rects);
+ helper_.InvalidateRegion(*updated_region);
+
+ // Capture the damaged portions of the desktop.
+ helper_.TakeInvalidRegion(updated_region);
+
+ for (DesktopRegion::Iterator it(*updated_region); !it.IsAtEnd();
+ it.Advance()) {
+ auto rect = it.rect();
+ rect.Translate(frame->top_left());
+ if (!x_server_pixel_buffer_.CaptureRect(rect, frame.get()))
+ return nullptr;
+ }
+ } else {
+ // Doing full-screen polling, or this is the first capture after a
+ // screen-resolution change. In either case, need a full-screen capture.
+ if (!x_server_pixel_buffer_.CaptureRect(selected_monitor_rect_,
+ frame.get())) {
+ return nullptr;
+ }
+ updated_region->SetRect(DesktopRect::MakeSize(frame->size()));
+ }
+
+ return std::move(frame);
+}
+
+void ScreenCapturerX11::ScreenConfigurationChanged() {
+ TRACE_EVENT0("webrtc", "ScreenCapturerX11::ScreenConfigurationChanged");
+ // Make sure the frame buffers will be reallocated.
+ queue_.Reset();
+
+ helper_.ClearInvalidRegion();
+ if (!x_server_pixel_buffer_.Init(atom_cache_.get(),
+ DefaultRootWindow(display()))) {
+ RTC_LOG(LS_ERROR) << "Failed to initialize pixel buffer after screen "
+ "configuration change.";
+ }
+
+ if (use_randr_) {
+ // Adding/removing RANDR monitors can generate a ConfigureNotify event
+ // without generating any RRScreenChangeNotify event. So it is important to
+ // update the monitors here even if the screen resolution hasn't changed.
+ UpdateMonitors();
+ } else {
+ selected_monitor_rect_ =
+ DesktopRect::MakeSize(x_server_pixel_buffer_.window_size());
+ }
+}
+
+void ScreenCapturerX11::SynchronizeFrame() {
+ // Synchronize the current buffer with the previous one since we do not
+ // capture the entire desktop. Note that encoder may be reading from the
+ // previous buffer at this time so thread access complaints are false
+ // positives.
+
+ // TODO(hclam): We can reduce the amount of copying here by subtracting
+ // `capturer_helper_`s region from `last_invalid_region_`.
+ // http://crbug.com/92354
+ RTC_DCHECK(queue_.previous_frame());
+
+ DesktopFrame* current = queue_.current_frame();
+ DesktopFrame* last = queue_.previous_frame();
+ RTC_DCHECK(current != last);
+ for (DesktopRegion::Iterator it(last_invalid_region_); !it.IsAtEnd();
+ it.Advance()) {
+ const DesktopRect& r = it.rect();
+ current->CopyPixelsFrom(*last, r.top_left(), r);
+ }
+}
+
+RTC_NO_SANITIZE("cfi-icall")
+void ScreenCapturerX11::DeinitXlib() {
+ if (monitors_) {
+ free_monitors_(monitors_);
+ monitors_ = nullptr;
+ }
+
+ if (gc_) {
+ XFreeGC(display(), gc_);
+ gc_ = nullptr;
+ }
+
+ x_server_pixel_buffer_.Release();
+
+ if (display()) {
+ if (damage_handle_) {
+ XDamageDestroy(display(), damage_handle_);
+ damage_handle_ = 0;
+ }
+
+ if (damage_region_) {
+ XFixesDestroyRegion(display(), damage_region_);
+ damage_region_ = 0;
+ }
+ }
+}
+
+// static
+std::unique_ptr<DesktopCapturer> ScreenCapturerX11::CreateRawScreenCapturer(
+ const DesktopCaptureOptions& options) {
+ if (!options.x_display())
+ return nullptr;
+
+ std::unique_ptr<ScreenCapturerX11> capturer(new ScreenCapturerX11());
+ if (!capturer.get()->Init(options)) {
+ return nullptr;
+ }
+
+ return std::move(capturer);
+}
+
+} // namespace webrtc
diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/x11/screen_capturer_x11.h b/third_party/libwebrtc/modules/desktop_capture/linux/x11/screen_capturer_x11.h
new file mode 100644
index 0000000000..d2a437aaa2
--- /dev/null
+++ b/third_party/libwebrtc/modules/desktop_capture/linux/x11/screen_capturer_x11.h
@@ -0,0 +1,147 @@
+/*
+ * 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_X11_SCREEN_CAPTURER_X11_H_
+#define MODULES_DESKTOP_CAPTURE_LINUX_X11_SCREEN_CAPTURER_X11_H_
+
+#include <X11/X.h>
+#include <X11/Xlib.h>
+#include <X11/extensions/Xdamage.h>
+#include <X11/extensions/Xfixes.h>
+#include <X11/extensions/Xrandr.h>
+
+#include <memory>
+
+#include "modules/desktop_capture/desktop_capture_options.h"
+#include "modules/desktop_capture/desktop_capturer.h"
+#include "modules/desktop_capture/desktop_frame.h"
+#include "modules/desktop_capture/desktop_region.h"
+#include "modules/desktop_capture/linux/x11/shared_x_display.h"
+#include "modules/desktop_capture/linux/x11/x_atom_cache.h"
+#include "modules/desktop_capture/linux/x11/x_server_pixel_buffer.h"
+#include "modules/desktop_capture/screen_capture_frame_queue.h"
+#include "modules/desktop_capture/screen_capturer_helper.h"
+#include "modules/desktop_capture/shared_desktop_frame.h"
+
+namespace webrtc {
+
+// A class to perform video frame capturing for Linux on X11.
+//
+// If XDamage is used, this class sets DesktopFrame::updated_region() according
+// to the areas reported by XDamage. Otherwise this class does not detect
+// DesktopFrame::updated_region(), the field is always set to the entire frame
+// rectangle. ScreenCapturerDifferWrapper should be used if that functionality
+// is necessary.
+class ScreenCapturerX11 : public DesktopCapturer,
+ public SharedXDisplay::XEventHandler {
+ public:
+ ScreenCapturerX11();
+ ~ScreenCapturerX11() override;
+
+ ScreenCapturerX11(const ScreenCapturerX11&) = delete;
+ ScreenCapturerX11& operator=(const ScreenCapturerX11&) = delete;
+
+ static std::unique_ptr<DesktopCapturer> CreateRawScreenCapturer(
+ const DesktopCaptureOptions& options);
+
+ // TODO(ajwong): Do we really want this to be synchronous?
+ bool Init(const DesktopCaptureOptions& options);
+
+ // DesktopCapturer interface.
+ void Start(Callback* delegate) override;
+ void CaptureFrame() override;
+ bool GetSourceList(SourceList* sources) override;
+ bool SelectSource(SourceId id) override;
+
+ private:
+ Display* display() { return options_.x_display()->display(); }
+
+ // SharedXDisplay::XEventHandler interface.
+ bool HandleXEvent(const XEvent& event) override;
+
+ void InitXDamage();
+ void InitXrandr();
+ void UpdateMonitors();
+
+ // Capture screen pixels to the current buffer in the queue. In the DAMAGE
+ // case, the ScreenCapturerHelper already holds the list of invalid rectangles
+ // from HandleXEvent(). In the non-DAMAGE case, this captures the
+ // whole screen, then calculates some invalid rectangles that include any
+ // differences between this and the previous capture.
+ std::unique_ptr<DesktopFrame> CaptureScreen();
+
+ // Called when the screen configuration is changed.
+ void ScreenConfigurationChanged();
+
+ // Synchronize the current buffer with `last_buffer_`, by copying pixels from
+ // the area of `last_invalid_rects`.
+ // Note this only works on the assumption that kNumBuffers == 2, as
+ // `last_invalid_rects` holds the differences from the previous buffer and
+ // the one prior to that (which will then be the current buffer).
+ void SynchronizeFrame();
+
+ void DeinitXlib();
+
+ DesktopCaptureOptions options_;
+
+ Callback* callback_ = nullptr;
+
+ // X11 graphics context.
+ GC gc_ = nullptr;
+ Window root_window_ = BadValue;
+
+ // XRandR 1.5 monitors.
+ bool use_randr_ = false;
+ int randr_event_base_ = 0;
+ XRRMonitorInfo* monitors_ = nullptr;
+ int num_monitors_ = 0;
+ DesktopRect selected_monitor_rect_;
+ // selected_monitor_name_ will be changed to kFullDesktopScreenId
+ // by a call to SelectSource() at the end of Init() because
+ // selected_monitor_rect_ should be updated as well.
+ // Setting it to kFullDesktopScreenId here might be misleading.
+ Atom selected_monitor_name_ = 0;
+ typedef XRRMonitorInfo* (*get_monitors_func)(Display*, Window, Bool, int*);
+ typedef void (*free_monitors_func)(XRRMonitorInfo*);
+ get_monitors_func get_monitors_ = nullptr;
+ free_monitors_func free_monitors_ = nullptr;
+
+ // XFixes.
+ bool has_xfixes_ = false;
+ int xfixes_event_base_ = -1;
+ int xfixes_error_base_ = -1;
+
+ // XDamage information.
+ bool use_damage_ = false;
+ Damage damage_handle_ = 0;
+ int damage_event_base_ = -1;
+ int damage_error_base_ = -1;
+ XserverRegion damage_region_ = 0;
+
+ // Access to the X Server's pixel buffer.
+ XServerPixelBuffer x_server_pixel_buffer_;
+
+ // A thread-safe list of invalid rectangles, and the size of the most
+ // recently captured screen.
+ ScreenCapturerHelper helper_;
+
+ // Queue of the frames buffers.
+ ScreenCaptureFrameQueue<SharedDesktopFrame> queue_;
+
+ // Invalid region from the previous capture. This is used to synchronize the
+ // current with the last buffer used.
+ DesktopRegion last_invalid_region_;
+
+ std::unique_ptr<XAtomCache> atom_cache_;
+};
+
+} // namespace webrtc
+
+#endif // MODULES_DESKTOP_CAPTURE_LINUX_X11_SCREEN_CAPTURER_X11_H_
diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/x11/shared_x_display.cc b/third_party/libwebrtc/modules/desktop_capture/linux/x11/shared_x_display.cc
new file mode 100644
index 0000000000..d690b0e2ba
--- /dev/null
+++ b/third_party/libwebrtc/modules/desktop_capture/linux/x11/shared_x_display.cc
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 2013 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/x11/shared_x_display.h"
+
+#include <X11/Xlib.h>
+#include <X11/extensions/XTest.h>
+
+#include <algorithm>
+
+#include "absl/strings/string_view.h"
+#include "rtc_base/checks.h"
+#include "rtc_base/logging.h"
+
+namespace webrtc {
+
+SharedXDisplay::SharedXDisplay(Display* display) : display_(display) {
+ RTC_DCHECK(display_);
+}
+
+SharedXDisplay::~SharedXDisplay() {
+ RTC_DCHECK(event_handlers_.empty());
+ XCloseDisplay(display_);
+}
+
+// static
+rtc::scoped_refptr<SharedXDisplay> SharedXDisplay::Create(
+ absl::string_view display_name) {
+ Display* display = XOpenDisplay(
+ display_name.empty() ? NULL : std::string(display_name).c_str());
+ if (!display) {
+ RTC_LOG(LS_ERROR) << "Unable to open display";
+ return nullptr;
+ }
+ return rtc::scoped_refptr<SharedXDisplay>(new SharedXDisplay(display));
+}
+
+// static
+rtc::scoped_refptr<SharedXDisplay> SharedXDisplay::CreateDefault() {
+ return Create(std::string());
+}
+
+void SharedXDisplay::AddEventHandler(int type, XEventHandler* handler) {
+ MutexLock lock(&mutex_);
+ event_handlers_[type].push_back(handler);
+}
+
+void SharedXDisplay::RemoveEventHandler(int type, XEventHandler* handler) {
+ MutexLock lock(&mutex_);
+ EventHandlersMap::iterator handlers = event_handlers_.find(type);
+ if (handlers == event_handlers_.end())
+ return;
+
+ std::vector<XEventHandler*>::iterator new_end =
+ std::remove(handlers->second.begin(), handlers->second.end(), handler);
+ handlers->second.erase(new_end, handlers->second.end());
+
+ // Check if no handlers left for this event.
+ if (handlers->second.empty())
+ event_handlers_.erase(handlers);
+}
+
+void SharedXDisplay::ProcessPendingXEvents() {
+ // Hold reference to `this` to prevent it from being destroyed while
+ // processing events.
+ rtc::scoped_refptr<SharedXDisplay> self(this);
+
+ // Protect access to `event_handlers_` after incrementing the refcount for
+ // `this` to ensure the instance is still valid when the lock is acquired.
+ MutexLock lock(&mutex_);
+
+ // Find the number of events that are outstanding "now." We don't just loop
+ // on XPending because we want to guarantee this terminates.
+ int events_to_process = XPending(display());
+ XEvent e;
+
+ for (int i = 0; i < events_to_process; i++) {
+ XNextEvent(display(), &e);
+ EventHandlersMap::iterator handlers = event_handlers_.find(e.type);
+ if (handlers == event_handlers_.end())
+ continue;
+ for (std::vector<XEventHandler*>::iterator it = handlers->second.begin();
+ it != handlers->second.end(); ++it) {
+ if ((*it)->HandleXEvent(e))
+ break;
+ }
+ }
+}
+
+void SharedXDisplay::IgnoreXServerGrabs() {
+ int test_event_base = 0;
+ int test_error_base = 0;
+ int major = 0;
+ int minor = 0;
+ if (XTestQueryExtension(display(), &test_event_base, &test_error_base, &major,
+ &minor)) {
+ XTestGrabControl(display(), true);
+ }
+}
+
+} // namespace webrtc
diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/x11/shared_x_display.h b/third_party/libwebrtc/modules/desktop_capture/linux/x11/shared_x_display.h
new file mode 100644
index 0000000000..c05fc46546
--- /dev/null
+++ b/third_party/libwebrtc/modules/desktop_capture/linux/x11/shared_x_display.h
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2013 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_X11_SHARED_X_DISPLAY_H_
+#define MODULES_DESKTOP_CAPTURE_LINUX_X11_SHARED_X_DISPLAY_H_
+
+#include <map>
+#include <vector>
+
+#include "absl/strings/string_view.h"
+#include "api/ref_counted_base.h"
+#include "api/scoped_refptr.h"
+#include "rtc_base/synchronization/mutex.h"
+#include "rtc_base/system/rtc_export.h"
+#include "rtc_base/thread_annotations.h"
+
+// Including Xlib.h will involve evil defines (Bool, Status, True, False), which
+// easily conflict with other headers.
+typedef struct _XDisplay Display;
+typedef union _XEvent XEvent;
+
+namespace webrtc {
+
+// A ref-counted object to store XDisplay connection.
+class RTC_EXPORT SharedXDisplay
+ : public rtc::RefCountedNonVirtual<SharedXDisplay> {
+ public:
+ class XEventHandler {
+ public:
+ virtual ~XEventHandler() {}
+
+ // Processes XEvent. Returns true if the event has been handled.
+ virtual bool HandleXEvent(const XEvent& event) = 0;
+ };
+
+ // Creates a new X11 Display for the `display_name`. NULL is returned if X11
+ // connection failed. Equivalent to CreateDefault() when `display_name` is
+ // empty.
+ static rtc::scoped_refptr<SharedXDisplay> Create(
+ absl::string_view display_name);
+
+ // Creates X11 Display connection for the default display (e.g. specified in
+ // DISPLAY). NULL is returned if X11 connection failed.
+ static rtc::scoped_refptr<SharedXDisplay> CreateDefault();
+
+ Display* display() { return display_; }
+
+ // Adds a new event `handler` for XEvent's of `type`.
+ void AddEventHandler(int type, XEventHandler* handler);
+
+ // Removes event `handler` added using `AddEventHandler`. Doesn't do anything
+ // if `handler` is not registered.
+ void RemoveEventHandler(int type, XEventHandler* handler);
+
+ // Processes pending XEvents, calling corresponding event handlers.
+ void ProcessPendingXEvents();
+
+ void IgnoreXServerGrabs();
+
+ ~SharedXDisplay();
+
+ SharedXDisplay(const SharedXDisplay&) = delete;
+ SharedXDisplay& operator=(const SharedXDisplay&) = delete;
+
+ protected:
+ // Takes ownership of `display`.
+ explicit SharedXDisplay(Display* display);
+
+ private:
+ typedef std::map<int, std::vector<XEventHandler*> > EventHandlersMap;
+
+ Display* display_;
+
+ Mutex mutex_;
+
+ EventHandlersMap event_handlers_ RTC_GUARDED_BY(mutex_);
+};
+
+} // namespace webrtc
+
+#endif // MODULES_DESKTOP_CAPTURE_LINUX_X11_SHARED_X_DISPLAY_H_
diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/x11/window_capturer_x11.cc b/third_party/libwebrtc/modules/desktop_capture/linux/x11/window_capturer_x11.cc
new file mode 100644
index 0000000000..2e17d44c0a
--- /dev/null
+++ b/third_party/libwebrtc/modules/desktop_capture/linux/x11/window_capturer_x11.cc
@@ -0,0 +1,256 @@
+/*
+ * Copyright (c) 2013 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/x11/window_capturer_x11.h"
+
+#include <X11/Xutil.h>
+#include <X11/extensions/Xcomposite.h>
+#include <X11/extensions/composite.h>
+#include <string.h>
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "api/scoped_refptr.h"
+#include "modules/desktop_capture/desktop_capture_types.h"
+#include "modules/desktop_capture/desktop_frame.h"
+#include "modules/desktop_capture/desktop_region.h"
+#include "modules/desktop_capture/linux/x11/shared_x_display.h"
+#include "modules/desktop_capture/linux/x11/window_finder_x11.h"
+#include "modules/desktop_capture/linux/x11/window_list_utils.h"
+#include "rtc_base/checks.h"
+#include "rtc_base/logging.h"
+#include "rtc_base/trace_event.h"
+
+namespace webrtc {
+
+WindowCapturerX11::WindowCapturerX11(const DesktopCaptureOptions& options)
+ : x_display_(options.x_display()),
+ atom_cache_(display()),
+ window_finder_(&atom_cache_) {
+ int event_base, error_base, major_version, minor_version;
+ if (XCompositeQueryExtension(display(), &event_base, &error_base) &&
+ XCompositeQueryVersion(display(), &major_version, &minor_version) &&
+ // XCompositeNameWindowPixmap() requires version 0.2
+ (major_version > 0 || minor_version >= 2)) {
+ has_composite_extension_ = true;
+ } else {
+ RTC_LOG(LS_INFO) << "Xcomposite extension not available or too old.";
+ }
+
+ x_display_->AddEventHandler(ConfigureNotify, this);
+}
+
+WindowCapturerX11::~WindowCapturerX11() {
+ x_display_->RemoveEventHandler(ConfigureNotify, this);
+}
+
+bool WindowCapturerX11::GetSourceList(SourceList* sources) {
+ return GetWindowList(&atom_cache_, [this, sources](::Window window) {
+ Source w;
+ w.id = window;
+ w.pid = (pid_t)GetWindowProcessID(window);
+ if (this->GetWindowTitle(window, &w.title)) {
+ sources->push_back(w);
+ }
+ return true;
+ });
+}
+
+bool WindowCapturerX11::SelectSource(SourceId id) {
+ if (!x_server_pixel_buffer_.Init(&atom_cache_, id))
+ return false;
+
+ // Tell the X server to send us window resizing events.
+ XSelectInput(display(), id, StructureNotifyMask);
+
+ selected_window_ = id;
+
+ // In addition to needing X11 server-side support for Xcomposite, it actually
+ // needs to be turned on for the window. If the user has modern
+ // hardware/drivers but isn't using a compositing window manager, that won't
+ // be the case. Here we automatically turn it on.
+
+ // Redirect drawing to an offscreen buffer (ie, turn on compositing). X11
+ // remembers who has requested this and will turn it off for us when we exit.
+ XCompositeRedirectWindow(display(), id, CompositeRedirectAutomatic);
+
+ return true;
+}
+
+bool WindowCapturerX11::FocusOnSelectedSource() {
+ if (!selected_window_)
+ return false;
+
+ unsigned int num_children;
+ ::Window* children;
+ ::Window parent;
+ ::Window root;
+ // Find the root window to pass event to.
+ int status = XQueryTree(display(), selected_window_, &root, &parent,
+ &children, &num_children);
+ if (status == 0) {
+ RTC_LOG(LS_ERROR) << "Failed to query for the root window.";
+ return false;
+ }
+
+ if (children)
+ XFree(children);
+
+ XRaiseWindow(display(), selected_window_);
+
+ // Some window managers (e.g., metacity in GNOME) consider it illegal to
+ // raise a window without also giving it input focus with
+ // _NET_ACTIVE_WINDOW, so XRaiseWindow() on its own isn't enough.
+ Atom atom = XInternAtom(display(), "_NET_ACTIVE_WINDOW", True);
+ if (atom != None) {
+ XEvent xev;
+ xev.xclient.type = ClientMessage;
+ xev.xclient.serial = 0;
+ xev.xclient.send_event = True;
+ xev.xclient.window = selected_window_;
+ xev.xclient.message_type = atom;
+
+ // The format member is set to 8, 16, or 32 and specifies whether the
+ // data should be viewed as a list of bytes, shorts, or longs.
+ xev.xclient.format = 32;
+
+ memset(xev.xclient.data.l, 0, sizeof(xev.xclient.data.l));
+
+ XSendEvent(display(), root, False,
+ SubstructureRedirectMask | SubstructureNotifyMask, &xev);
+ }
+ XFlush(display());
+ return true;
+}
+
+void WindowCapturerX11::Start(Callback* callback) {
+ RTC_DCHECK(!callback_);
+ RTC_DCHECK(callback);
+
+ callback_ = callback;
+}
+
+void WindowCapturerX11::CaptureFrame() {
+ TRACE_EVENT0("webrtc", "WindowCapturerX11::CaptureFrame");
+ x_display_->ProcessPendingXEvents();
+
+ if (!x_server_pixel_buffer_.IsWindowValid()) {
+ RTC_LOG(LS_ERROR) << "The window is no longer valid.";
+ callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr);
+ return;
+ }
+
+ if (!has_composite_extension_) {
+ // Without the Xcomposite extension we capture when the whole window is
+ // visible on screen and not covered by any other window. This is not
+ // something we want so instead, just bail out.
+ RTC_LOG(LS_ERROR) << "No Xcomposite extension detected.";
+ callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr);
+ return;
+ }
+
+ if (GetWindowState(&atom_cache_, selected_window_) == IconicState) {
+ // Window is in minimized. Return a 1x1 frame as same as OSX/Win does.
+ std::unique_ptr<DesktopFrame> frame(
+ new BasicDesktopFrame(DesktopSize(1, 1)));
+ callback_->OnCaptureResult(Result::SUCCESS, std::move(frame));
+ return;
+ }
+
+ std::unique_ptr<DesktopFrame> frame(
+ new BasicDesktopFrame(x_server_pixel_buffer_.window_size()));
+
+ x_server_pixel_buffer_.Synchronize();
+ if (!x_server_pixel_buffer_.CaptureRect(DesktopRect::MakeSize(frame->size()),
+ frame.get())) {
+ RTC_LOG(LS_WARNING) << "Temporarily failed to capture winodw.";
+ callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr);
+ return;
+ }
+
+ frame->mutable_updated_region()->SetRect(
+ DesktopRect::MakeSize(frame->size()));
+ frame->set_top_left(x_server_pixel_buffer_.window_rect().top_left());
+ frame->set_capturer_id(DesktopCapturerId::kX11CapturerLinux);
+
+ callback_->OnCaptureResult(Result::SUCCESS, std::move(frame));
+}
+
+bool WindowCapturerX11::IsOccluded(const DesktopVector& pos) {
+ return window_finder_.GetWindowUnderPoint(pos) !=
+ static_cast<WindowId>(selected_window_);
+}
+
+bool WindowCapturerX11::HandleXEvent(const XEvent& event) {
+ if (event.type == ConfigureNotify) {
+ XConfigureEvent xce = event.xconfigure;
+ if (xce.window == selected_window_) {
+ if (!DesktopRectFromXAttributes(xce).equals(
+ x_server_pixel_buffer_.window_rect())) {
+ if (!x_server_pixel_buffer_.Init(&atom_cache_, selected_window_)) {
+ RTC_LOG(LS_ERROR)
+ << "Failed to initialize pixel buffer after resizing.";
+ }
+ }
+ }
+ }
+
+ // Always returns false, so other observers can still receive the events.
+ return false;
+}
+
+bool WindowCapturerX11::GetWindowTitle(::Window window, std::string* title) {
+ int status;
+ bool result = false;
+ XTextProperty window_name;
+ window_name.value = nullptr;
+ if (window) {
+ status = XGetWMName(display(), window, &window_name);
+ if (status && window_name.value && window_name.nitems) {
+ int cnt;
+ char** list = nullptr;
+ status =
+ Xutf8TextPropertyToTextList(display(), &window_name, &list, &cnt);
+ if (status >= Success && cnt && *list) {
+ if (cnt > 1) {
+ RTC_LOG(LS_INFO) << "Window has " << cnt
+ << " text properties, only using the first one.";
+ }
+ *title = *list;
+ result = true;
+ }
+ if (list)
+ XFreeStringList(list);
+ }
+ if (window_name.value)
+ XFree(window_name.value);
+ }
+ return result;
+}
+
+int WindowCapturerX11::GetWindowProcessID(::Window window) {
+ // Get _NET_WM_PID property of the window.
+ Atom process_atom = XInternAtom(display(), "_NET_WM_PID", True);
+ XWindowProperty<uint32_t> process_id(display(), window, process_atom);
+
+ return process_id.is_valid() ? *process_id.data() : 0;
+}
+
+// static
+std::unique_ptr<DesktopCapturer> WindowCapturerX11::CreateRawWindowCapturer(
+ const DesktopCaptureOptions& options) {
+ if (!options.x_display())
+ return nullptr;
+ return std::unique_ptr<DesktopCapturer>(new WindowCapturerX11(options));
+}
+
+} // namespace webrtc
diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/x11/window_capturer_x11.h b/third_party/libwebrtc/modules/desktop_capture/linux/x11/window_capturer_x11.h
new file mode 100644
index 0000000000..cfd29eca66
--- /dev/null
+++ b/third_party/libwebrtc/modules/desktop_capture/linux/x11/window_capturer_x11.h
@@ -0,0 +1,78 @@
+/*
+ * 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_X11_WINDOW_CAPTURER_X11_H_
+#define MODULES_DESKTOP_CAPTURE_LINUX_X11_WINDOW_CAPTURER_X11_H_
+
+#include <X11/X.h>
+#include <X11/Xlib.h>
+
+#include <memory>
+#include <string>
+
+#include "api/scoped_refptr.h"
+#include "modules/desktop_capture/desktop_capture_options.h"
+#include "modules/desktop_capture/desktop_capturer.h"
+#include "modules/desktop_capture/desktop_geometry.h"
+#include "modules/desktop_capture/linux/x11/shared_x_display.h"
+#include "modules/desktop_capture/linux/x11/x_window_property.h"
+#include "modules/desktop_capture/linux/x11/window_finder_x11.h"
+#include "modules/desktop_capture/linux/x11/x_atom_cache.h"
+#include "modules/desktop_capture/linux/x11/x_server_pixel_buffer.h"
+
+namespace webrtc {
+
+class WindowCapturerX11 : public DesktopCapturer,
+ public SharedXDisplay::XEventHandler {
+ public:
+ explicit WindowCapturerX11(const DesktopCaptureOptions& options);
+ ~WindowCapturerX11() override;
+
+ WindowCapturerX11(const WindowCapturerX11&) = delete;
+ WindowCapturerX11& operator=(const WindowCapturerX11&) = delete;
+
+ static std::unique_ptr<DesktopCapturer> CreateRawWindowCapturer(
+ const DesktopCaptureOptions& options);
+
+ // DesktopCapturer interface.
+ void Start(Callback* callback) override;
+ void CaptureFrame() override;
+ bool GetSourceList(SourceList* sources) override;
+ bool SelectSource(SourceId id) override;
+ bool FocusOnSelectedSource() override;
+ bool IsOccluded(const DesktopVector& pos) override;
+
+ // SharedXDisplay::XEventHandler interface.
+ bool HandleXEvent(const XEvent& event) override;
+
+ private:
+ Display* display() { return x_display_->display(); }
+
+ // Returns window title for the specified X `window`.
+ bool GetWindowTitle(::Window window, std::string* title);
+
+ // Returns the id of the owning process.
+ int GetWindowProcessID(::Window window);
+
+ Callback* callback_ = nullptr;
+
+ rtc::scoped_refptr<SharedXDisplay> x_display_;
+
+ bool has_composite_extension_ = false;
+
+ ::Window selected_window_ = 0;
+ XServerPixelBuffer x_server_pixel_buffer_;
+ XAtomCache atom_cache_;
+ WindowFinderX11 window_finder_;
+};
+
+} // namespace webrtc
+
+#endif // MODULES_DESKTOP_CAPTURE_LINUX_X11_WINDOW_CAPTURER_X11_H_
diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/x11/window_finder_x11.cc b/third_party/libwebrtc/modules/desktop_capture/linux/x11/window_finder_x11.cc
new file mode 100644
index 0000000000..dec17ab51f
--- /dev/null
+++ b/third_party/libwebrtc/modules/desktop_capture/linux/x11/window_finder_x11.cc
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2017 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/x11/window_finder_x11.h"
+
+#include <X11/X.h>
+
+#include <memory>
+
+#include "modules/desktop_capture/linux/x11/window_list_utils.h"
+#include "rtc_base/checks.h"
+
+namespace webrtc {
+
+WindowFinderX11::WindowFinderX11(XAtomCache* cache) : cache_(cache) {
+ RTC_DCHECK(cache_);
+}
+
+WindowFinderX11::~WindowFinderX11() = default;
+
+WindowId WindowFinderX11::GetWindowUnderPoint(DesktopVector point) {
+ WindowId id = kNullWindowId;
+ GetWindowList(cache_, [&id, this, point](::Window window) {
+ DesktopRect rect;
+ if (GetWindowRect(this->cache_->display(), window, &rect) &&
+ rect.Contains(point)) {
+ id = window;
+ return false;
+ }
+ return true;
+ });
+ return id;
+}
+
+// static
+std::unique_ptr<WindowFinder> WindowFinder::Create(
+ const WindowFinder::Options& options) {
+ if (options.cache == nullptr) {
+ return nullptr;
+ }
+
+ return std::make_unique<WindowFinderX11>(options.cache);
+}
+
+} // namespace webrtc
diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/x11/window_finder_x11.h b/third_party/libwebrtc/modules/desktop_capture/linux/x11/window_finder_x11.h
new file mode 100644
index 0000000000..91de876417
--- /dev/null
+++ b/third_party/libwebrtc/modules/desktop_capture/linux/x11/window_finder_x11.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2017 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_X11_WINDOW_FINDER_X11_H_
+#define MODULES_DESKTOP_CAPTURE_LINUX_X11_WINDOW_FINDER_X11_H_
+
+#include "modules/desktop_capture/window_finder.h"
+
+namespace webrtc {
+
+class XAtomCache;
+
+// The implementation of WindowFinder for X11.
+class WindowFinderX11 final : public WindowFinder {
+ public:
+ explicit WindowFinderX11(XAtomCache* cache);
+ ~WindowFinderX11() override;
+
+ // WindowFinder implementation.
+ WindowId GetWindowUnderPoint(DesktopVector point) override;
+
+ private:
+ XAtomCache* const cache_;
+};
+
+} // namespace webrtc
+
+#endif // MODULES_DESKTOP_CAPTURE_LINUX_X11_WINDOW_FINDER_X11_H_
diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/x11/window_list_utils.cc b/third_party/libwebrtc/modules/desktop_capture/linux/x11/window_list_utils.cc
new file mode 100644
index 0000000000..ff2d467e29
--- /dev/null
+++ b/third_party/libwebrtc/modules/desktop_capture/linux/x11/window_list_utils.cc
@@ -0,0 +1,198 @@
+/*
+ * Copyright (c) 2017 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/x11/window_list_utils.h"
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <string.h>
+
+#include <algorithm>
+
+#include "modules/desktop_capture/linux/x11/x_error_trap.h"
+#include "modules/desktop_capture/linux/x11/x_window_property.h"
+#include "rtc_base/checks.h"
+#include "rtc_base/logging.h"
+
+namespace webrtc {
+
+namespace {
+
+class DeferXFree {
+ public:
+ explicit DeferXFree(void* data) : data_(data) {}
+ ~DeferXFree();
+
+ private:
+ void* const data_;
+};
+
+DeferXFree::~DeferXFree() {
+ if (data_)
+ XFree(data_);
+}
+
+// Iterates through `window` hierarchy to find first visible window, i.e. one
+// that has WM_STATE property set to NormalState.
+// See http://tronche.com/gui/x/icccm/sec-4.html#s-4.1.3.1 .
+::Window GetApplicationWindow(XAtomCache* cache, ::Window window) {
+ int32_t state = GetWindowState(cache, window);
+ if (state == NormalState) {
+ // Window has WM_STATE==NormalState. Return it.
+ return window;
+ } else if (state == IconicState) {
+ // Window is in minimized. Skip it.
+ return 0;
+ }
+
+ RTC_DCHECK_EQ(state, WithdrawnState);
+ // If the window is in WithdrawnState then look at all of its children.
+ ::Window root, parent;
+ ::Window* children;
+ unsigned int num_children;
+ if (!XQueryTree(cache->display(), window, &root, &parent, &children,
+ &num_children)) {
+ RTC_LOG(LS_ERROR) << "Failed to query for child windows although window"
+ "does not have a valid WM_STATE.";
+ return 0;
+ }
+ ::Window app_window = 0;
+ for (unsigned int i = 0; i < num_children; ++i) {
+ app_window = GetApplicationWindow(cache, children[i]);
+ if (app_window)
+ break;
+ }
+
+ if (children)
+ XFree(children);
+ return app_window;
+}
+
+// Returns true if the `window` is a desktop element.
+bool IsDesktopElement(XAtomCache* cache, ::Window window) {
+ RTC_DCHECK(cache);
+ if (window == 0)
+ return false;
+
+ // First look for _NET_WM_WINDOW_TYPE. The standard
+ // (http://standards.freedesktop.org/wm-spec/latest/ar01s05.html#id2760306)
+ // says this hint *should* be present on all windows, and we use the existence
+ // of _NET_WM_WINDOW_TYPE_NORMAL in the property to indicate a window is not
+ // a desktop element (that is, only "normal" windows should be shareable).
+ XWindowProperty<uint32_t> window_type(cache->display(), window,
+ cache->WindowType());
+ if (window_type.is_valid() && window_type.size() > 0) {
+ uint32_t* end = window_type.data() + window_type.size();
+ bool is_normal =
+ (end != std::find(window_type.data(), end, cache->WindowTypeNormal()));
+ return !is_normal;
+ }
+
+ // Fall back on using the hint.
+ XClassHint class_hint;
+ Status status = XGetClassHint(cache->display(), window, &class_hint);
+ if (status == 0) {
+ // No hints, assume this is a normal application window.
+ return false;
+ }
+
+ DeferXFree free_res_name(class_hint.res_name);
+ DeferXFree free_res_class(class_hint.res_class);
+ return strcmp("gnome-panel", class_hint.res_name) == 0 ||
+ strcmp("desktop_window", class_hint.res_name) == 0;
+}
+
+} // namespace
+
+int32_t GetWindowState(XAtomCache* cache, ::Window window) {
+ // Get WM_STATE property of the window.
+ XWindowProperty<uint32_t> window_state(cache->display(), window,
+ cache->WmState());
+
+ // WM_STATE is considered to be set to WithdrawnState when it missing.
+ return window_state.is_valid() ? *window_state.data() : WithdrawnState;
+}
+
+bool GetWindowList(XAtomCache* cache,
+ rtc::FunctionView<bool(::Window)> on_window) {
+ RTC_DCHECK(cache);
+ RTC_DCHECK(on_window);
+ ::Display* const display = cache->display();
+
+ int failed_screens = 0;
+ const int num_screens = XScreenCount(display);
+ for (int screen = 0; screen < num_screens; screen++) {
+ ::Window root_window = XRootWindow(display, screen);
+ ::Window parent;
+ ::Window* children;
+ unsigned int num_children;
+ {
+ XErrorTrap error_trap(display);
+ if (XQueryTree(display, root_window, &root_window, &parent, &children,
+ &num_children) == 0 ||
+ error_trap.GetLastErrorAndDisable() != 0) {
+ failed_screens++;
+ RTC_LOG(LS_ERROR) << "Failed to query for child windows for screen "
+ << screen;
+ continue;
+ }
+ }
+
+ DeferXFree free_children(children);
+
+ for (unsigned int i = 0; i < num_children; i++) {
+ // Iterates in reverse order to return windows from front to back.
+ ::Window app_window =
+ GetApplicationWindow(cache, children[num_children - 1 - i]);
+ if (app_window && !IsDesktopElement(cache, app_window)) {
+ if (!on_window(app_window)) {
+ return true;
+ }
+ }
+ }
+ }
+
+ return failed_screens < num_screens;
+}
+
+bool GetWindowRect(::Display* display,
+ ::Window window,
+ DesktopRect* rect,
+ XWindowAttributes* attributes /* = nullptr */) {
+ XWindowAttributes local_attributes;
+ int offset_x;
+ int offset_y;
+ if (attributes == nullptr) {
+ attributes = &local_attributes;
+ }
+
+ {
+ XErrorTrap error_trap(display);
+ if (!XGetWindowAttributes(display, window, attributes) ||
+ error_trap.GetLastErrorAndDisable() != 0) {
+ return false;
+ }
+ }
+ *rect = DesktopRectFromXAttributes(*attributes);
+
+ {
+ XErrorTrap error_trap(display);
+ ::Window child;
+ if (!XTranslateCoordinates(display, window, attributes->root, -rect->left(),
+ -rect->top(), &offset_x, &offset_y, &child) ||
+ error_trap.GetLastErrorAndDisable() != 0) {
+ return false;
+ }
+ }
+ rect->Translate(offset_x, offset_y);
+ return true;
+}
+
+} // namespace webrtc
diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/x11/window_list_utils.h b/third_party/libwebrtc/modules/desktop_capture/linux/x11/window_list_utils.h
new file mode 100644
index 0000000000..923842df14
--- /dev/null
+++ b/third_party/libwebrtc/modules/desktop_capture/linux/x11/window_list_utils.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2017 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_X11_WINDOW_LIST_UTILS_H_
+#define MODULES_DESKTOP_CAPTURE_LINUX_X11_WINDOW_LIST_UTILS_H_
+
+#include <X11/X.h>
+#include <X11/Xlib.h>
+#include <stdint.h>
+
+#include "api/function_view.h"
+#include "modules/desktop_capture/desktop_geometry.h"
+#include "modules/desktop_capture/linux/x11/x_atom_cache.h"
+
+namespace webrtc {
+
+// Synchronously iterates all on-screen windows in `cache`.display() in
+// decreasing z-order and sends them one-by-one to `on_window` function before
+// GetWindowList() returns. If `on_window` returns false, this function ignores
+// other windows and returns immediately. GetWindowList() returns false if
+// native APIs failed. If multiple screens are attached to the `display`, this
+// function returns false only when native APIs failed on all screens. Menus,
+// panels and minimized windows will be ignored.
+bool GetWindowList(XAtomCache* cache,
+ rtc::FunctionView<bool(::Window)> on_window);
+
+// Returns WM_STATE property of the `window`. This function returns
+// WithdrawnState if the `window` is missing.
+int32_t GetWindowState(XAtomCache* cache, ::Window window);
+
+// Returns the rectangle of the `window` in the coordinates of `display`. This
+// function returns false if native APIs failed. If `attributes` is provided, it
+// will be filled with the attributes of `window`. The `rect` is in system
+// coordinate, i.e. the primary monitor always starts from (0, 0).
+bool GetWindowRect(::Display* display,
+ ::Window window,
+ DesktopRect* rect,
+ XWindowAttributes* attributes = nullptr);
+
+// Creates a DesktopRect from `attributes`.
+template <typename T>
+DesktopRect DesktopRectFromXAttributes(const T& attributes) {
+ return DesktopRect::MakeXYWH(attributes.x, attributes.y, attributes.width,
+ attributes.height);
+}
+
+} // namespace webrtc
+
+#endif // MODULES_DESKTOP_CAPTURE_LINUX_X11_WINDOW_LIST_UTILS_H_
diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_atom_cache.cc b/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_atom_cache.cc
new file mode 100644
index 0000000000..157ba8b8fd
--- /dev/null
+++ b/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_atom_cache.cc
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2017 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/x11/x_atom_cache.h"
+
+#include "rtc_base/checks.h"
+
+namespace webrtc {
+
+XAtomCache::XAtomCache(::Display* display) : display_(display) {
+ RTC_DCHECK(display_);
+}
+
+XAtomCache::~XAtomCache() = default;
+
+::Display* XAtomCache::display() const {
+ return display_;
+}
+
+Atom XAtomCache::WmState() {
+ return CreateIfNotExist(&wm_state_, "WM_STATE");
+}
+
+Atom XAtomCache::WindowType() {
+ return CreateIfNotExist(&window_type_, "_NET_WM_WINDOW_TYPE");
+}
+
+Atom XAtomCache::WindowTypeNormal() {
+ return CreateIfNotExist(&window_type_normal_, "_NET_WM_WINDOW_TYPE_NORMAL");
+}
+
+Atom XAtomCache::IccProfile() {
+ return CreateIfNotExist(&icc_profile_, "_ICC_PROFILE");
+}
+
+Atom XAtomCache::CreateIfNotExist(Atom* atom, const char* name) {
+ RTC_DCHECK(atom);
+ if (*atom == None) {
+ *atom = XInternAtom(display(), name, True);
+ }
+ return *atom;
+}
+
+} // namespace webrtc
diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_atom_cache.h b/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_atom_cache.h
new file mode 100644
index 0000000000..39d957e98b
--- /dev/null
+++ b/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_atom_cache.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2017 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_X11_X_ATOM_CACHE_H_
+#define MODULES_DESKTOP_CAPTURE_LINUX_X11_X_ATOM_CACHE_H_
+
+#include <X11/X.h>
+#include <X11/Xlib.h>
+
+namespace webrtc {
+
+// A cache of Atom. Each Atom object is created on demand.
+class XAtomCache final {
+ public:
+ explicit XAtomCache(::Display* display);
+ ~XAtomCache();
+
+ ::Display* display() const;
+
+ Atom WmState();
+ Atom WindowType();
+ Atom WindowTypeNormal();
+ Atom IccProfile();
+
+ private:
+ // If |*atom| is None, this function uses XInternAtom() to retrieve an Atom.
+ Atom CreateIfNotExist(Atom* atom, const char* name);
+
+ ::Display* const display_;
+ Atom wm_state_ = None;
+ Atom window_type_ = None;
+ Atom window_type_normal_ = None;
+ Atom icc_profile_ = None;
+};
+
+} // namespace webrtc
+
+#endif // MODULES_DESKTOP_CAPTURE_LINUX_X11_X_ATOM_CACHE_H_
diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_error_trap.cc b/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_error_trap.cc
new file mode 100644
index 0000000000..3314dd286c
--- /dev/null
+++ b/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_error_trap.cc
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2013 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/x11/x_error_trap.h"
+
+#include <stddef.h>
+
+#include <limits>
+
+#include "rtc_base/checks.h"
+
+
+namespace webrtc {
+
+Bool XErrorTrap::XServerErrorHandler(Display* display, xReply* rep,
+ char* /* buf */, int /* len */,
+ XPointer data) {
+ XErrorTrap* self = reinterpret_cast<XErrorTrap*>(data);
+ if (rep->generic.type != X_Error ||
+ // Overflow-safe last_request_read <= last_ignored_request_ for skipping
+ // async replies from requests before XErrorTrap was created.
+ self->last_ignored_request_ - display->last_request_read <
+ std::numeric_limits<unsigned long>::max() >> 1)
+ return False;
+ self->last_xserver_error_code_ = rep->error.errorCode;
+ return True;
+}
+
+XErrorTrap::XErrorTrap(Display* display)
+ : display_(display),
+ last_xserver_error_code_(0),
+ enabled_(true) {
+ // Use async_handlers instead of XSetErrorHandler(). async_handlers can
+ // remain in place and then be safely removed at the right time even if a
+ // handler change happens concurrently on another thread. async_handlers
+ // are processed first and so can prevent errors reaching the global
+ // XSetErrorHandler handler. They also will not see errors from or affect
+ // handling of errors on other Displays, which may be processed on other
+ // threads.
+ LockDisplay(display);
+ async_handler_.next = display->async_handlers;
+ async_handler_.handler = XServerErrorHandler;
+ async_handler_.data = reinterpret_cast<XPointer>(this);
+ display->async_handlers = &async_handler_;
+ last_ignored_request_ = display->request;
+ UnlockDisplay(display);
+}
+
+int XErrorTrap::GetLastErrorAndDisable() {
+ assert(enabled_);
+ enabled_ = false;
+ LockDisplay(display_);
+ DeqAsyncHandler(display_, &async_handler_);
+ UnlockDisplay(display_);
+ return last_xserver_error_code_;
+}
+
+XErrorTrap::~XErrorTrap() {
+ if (enabled_)
+ GetLastErrorAndDisable();
+}
+
+} // namespace webrtc
diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_error_trap.h b/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_error_trap.h
new file mode 100644
index 0000000000..df7e86bf03
--- /dev/null
+++ b/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_error_trap.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2013 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_X11_X_ERROR_TRAP_H_
+#define MODULES_DESKTOP_CAPTURE_LINUX_X11_X_ERROR_TRAP_H_
+
+#include <X11/Xlibint.h>
+#undef max // Xlibint.h defines this and it breaks std::max
+#undef min // Xlibint.h defines this and it breaks std::min
+
+namespace webrtc {
+
+// Helper class that registers X Window error handler. Caller can use
+// GetLastErrorAndDisable() to get the last error that was caught, if any.
+// An XErrorTrap may be constructed on any thread, but errors are collected
+// from all threads and so |display| should be used only on one thread.
+// Other Displays are unaffected.
+class XErrorTrap {
+ public:
+ explicit XErrorTrap(Display* display);
+ ~XErrorTrap();
+
+ XErrorTrap(const XErrorTrap&) = delete;
+ XErrorTrap& operator=(const XErrorTrap&) = delete;
+
+ // Returns last error and removes unregisters the error handler.
+ // Must not be called more than once.
+ int GetLastErrorAndDisable();
+
+ private:
+ static Bool XServerErrorHandler(Display* display, xReply* rep,
+ char* /* buf */, int /* len */,
+ XPointer data);
+
+ _XAsyncHandler async_handler_;
+ Display* display_;
+ unsigned long last_ignored_request_;
+ int last_xserver_error_code_;
+ bool enabled_;
+};
+
+} // namespace webrtc
+
+#endif // MODULES_DESKTOP_CAPTURE_LINUX_X11_X_ERROR_TRAP_H_
diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_server_pixel_buffer.cc b/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_server_pixel_buffer.cc
new file mode 100644
index 0000000000..fd6fc7daf4
--- /dev/null
+++ b/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_server_pixel_buffer.cc
@@ -0,0 +1,379 @@
+/*
+ * Copyright (c) 2013 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/x11/x_server_pixel_buffer.h"
+
+#include <X11/Xutil.h>
+#include <stdint.h>
+#include <string.h>
+#include <sys/ipc.h>
+#include <sys/shm.h>
+
+#include "modules/desktop_capture/desktop_frame.h"
+#include "modules/desktop_capture/linux/x11/window_list_utils.h"
+#include "modules/desktop_capture/linux/x11/x_error_trap.h"
+#include "modules/desktop_capture/linux/x11/x_window_property.h"
+#include "rtc_base/checks.h"
+#include "rtc_base/logging.h"
+
+namespace webrtc {
+
+namespace {
+
+// Returns the number of bits `mask` has to be shifted left so its last
+// (most-significant) bit set becomes the most-significant bit of the word.
+// When `mask` is 0 the function returns 31.
+uint32_t MaskToShift(uint32_t mask) {
+ int shift = 0;
+ if ((mask & 0xffff0000u) == 0) {
+ mask <<= 16;
+ shift += 16;
+ }
+ if ((mask & 0xff000000u) == 0) {
+ mask <<= 8;
+ shift += 8;
+ }
+ if ((mask & 0xf0000000u) == 0) {
+ mask <<= 4;
+ shift += 4;
+ }
+ if ((mask & 0xc0000000u) == 0) {
+ mask <<= 2;
+ shift += 2;
+ }
+ if ((mask & 0x80000000u) == 0)
+ shift += 1;
+
+ return shift;
+}
+
+// Returns true if `image` is in RGB format.
+bool IsXImageRGBFormat(XImage* image) {
+ return image->bits_per_pixel == 32 && image->red_mask == 0xff0000 &&
+ image->green_mask == 0xff00 && image->blue_mask == 0xff;
+}
+
+// We expose two forms of blitting to handle variations in the pixel format.
+// In FastBlit(), the operation is effectively a memcpy.
+void FastBlit(XImage* x_image,
+ uint8_t* src_pos,
+ const DesktopRect& rect,
+ DesktopFrame* frame) {
+ RTC_DCHECK_LE(frame->top_left().x(), rect.left());
+ RTC_DCHECK_LE(frame->top_left().y(), rect.top());
+
+ int src_stride = x_image->bytes_per_line;
+ int dst_x = rect.left() - frame->top_left().x();
+ int dst_y = rect.top() - frame->top_left().y();
+
+ uint8_t* dst_pos = frame->data() + frame->stride() * dst_y;
+ dst_pos += dst_x * DesktopFrame::kBytesPerPixel;
+
+ int height = rect.height();
+ int row_bytes = rect.width() * DesktopFrame::kBytesPerPixel;
+ for (int y = 0; y < height; ++y) {
+ memcpy(dst_pos, src_pos, row_bytes);
+ src_pos += src_stride;
+ dst_pos += frame->stride();
+ }
+}
+
+void SlowBlit(XImage* x_image,
+ uint8_t* src_pos,
+ const DesktopRect& rect,
+ DesktopFrame* frame) {
+ RTC_DCHECK_LE(frame->top_left().x(), rect.left());
+ RTC_DCHECK_LE(frame->top_left().y(), rect.top());
+
+ int src_stride = x_image->bytes_per_line;
+ int dst_x = rect.left() - frame->top_left().x();
+ int dst_y = rect.top() - frame->top_left().y();
+ int width = rect.width(), height = rect.height();
+
+ uint32_t red_mask = x_image->red_mask;
+ uint32_t green_mask = x_image->red_mask;
+ uint32_t blue_mask = x_image->blue_mask;
+
+ uint32_t red_shift = MaskToShift(red_mask);
+ uint32_t green_shift = MaskToShift(green_mask);
+ uint32_t blue_shift = MaskToShift(blue_mask);
+
+ int bits_per_pixel = x_image->bits_per_pixel;
+
+ uint8_t* dst_pos = frame->data() + frame->stride() * dst_y;
+ dst_pos += dst_x * DesktopFrame::kBytesPerPixel;
+ // TODO(hclam): Optimize, perhaps using MMX code or by converting to
+ // YUV directly.
+ // TODO(sergeyu): This code doesn't handle XImage byte order properly and
+ // won't work with 24bpp images. Fix it.
+ for (int y = 0; y < height; y++) {
+ uint32_t* dst_pos_32 = reinterpret_cast<uint32_t*>(dst_pos);
+ uint32_t* src_pos_32 = reinterpret_cast<uint32_t*>(src_pos);
+ uint16_t* src_pos_16 = reinterpret_cast<uint16_t*>(src_pos);
+ for (int x = 0; x < width; x++) {
+ // Dereference through an appropriately-aligned pointer.
+ uint32_t pixel;
+ if (bits_per_pixel == 32) {
+ pixel = src_pos_32[x];
+ } else if (bits_per_pixel == 16) {
+ pixel = src_pos_16[x];
+ } else {
+ pixel = src_pos[x];
+ }
+ uint32_t r = (pixel & red_mask) << red_shift;
+ uint32_t g = (pixel & green_mask) << green_shift;
+ uint32_t b = (pixel & blue_mask) << blue_shift;
+ // Write as 32-bit RGB.
+ dst_pos_32[x] =
+ ((r >> 8) & 0xff0000) | ((g >> 16) & 0xff00) | ((b >> 24) & 0xff);
+ }
+ dst_pos += frame->stride();
+ src_pos += src_stride;
+ }
+}
+
+} // namespace
+
+XServerPixelBuffer::XServerPixelBuffer() {}
+
+XServerPixelBuffer::~XServerPixelBuffer() {
+ Release();
+}
+
+void XServerPixelBuffer::Release() {
+ if (x_image_) {
+ XDestroyImage(x_image_);
+ x_image_ = nullptr;
+ }
+ if (x_shm_image_) {
+ XDestroyImage(x_shm_image_);
+ x_shm_image_ = nullptr;
+ }
+ if (shm_pixmap_) {
+ XFreePixmap(display_, shm_pixmap_);
+ shm_pixmap_ = 0;
+ }
+ if (shm_gc_) {
+ XFreeGC(display_, shm_gc_);
+ shm_gc_ = nullptr;
+ }
+
+ ReleaseSharedMemorySegment();
+
+ window_ = 0;
+}
+
+void XServerPixelBuffer::ReleaseSharedMemorySegment() {
+ if (!shm_segment_info_)
+ return;
+ if (shm_segment_info_->shmaddr != nullptr)
+ shmdt(shm_segment_info_->shmaddr);
+ if (shm_segment_info_->shmid != -1)
+ shmctl(shm_segment_info_->shmid, IPC_RMID, 0);
+ delete shm_segment_info_;
+ shm_segment_info_ = nullptr;
+}
+
+bool XServerPixelBuffer::Init(XAtomCache* cache, Window window) {
+ Release();
+ display_ = cache->display();
+
+ XWindowAttributes attributes;
+ if (!GetWindowRect(display_, window, &window_rect_, &attributes)) {
+ return false;
+ }
+
+ if (cache->IccProfile() != None) {
+ // `window` is the root window when doing screen capture.
+ XWindowProperty<uint8_t> icc_profile_property(cache->display(), window,
+ cache->IccProfile());
+ if (icc_profile_property.is_valid() && icc_profile_property.size() > 0) {
+ icc_profile_ = std::vector<uint8_t>(
+ icc_profile_property.data(),
+ icc_profile_property.data() + icc_profile_property.size());
+ } else {
+ RTC_LOG(LS_WARNING) << "Failed to get icc profile";
+ }
+ }
+
+ window_ = window;
+ InitShm(attributes);
+
+ return true;
+}
+
+void XServerPixelBuffer::InitShm(const XWindowAttributes& attributes) {
+ Visual* default_visual = attributes.visual;
+ int default_depth = attributes.depth;
+
+ int major, minor;
+ Bool have_pixmaps;
+ if (!XShmQueryVersion(display_, &major, &minor, &have_pixmaps)) {
+ // Shared memory not supported. CaptureRect will use the XImage API instead.
+ return;
+ }
+
+ bool using_shm = false;
+ shm_segment_info_ = new XShmSegmentInfo;
+ shm_segment_info_->shmid = -1;
+ shm_segment_info_->shmaddr = nullptr;
+ shm_segment_info_->readOnly = False;
+ x_shm_image_ = XShmCreateImage(display_, default_visual, default_depth,
+ ZPixmap, 0, shm_segment_info_,
+ window_rect_.width(), window_rect_.height());
+ if (x_shm_image_) {
+ shm_segment_info_->shmid =
+ shmget(IPC_PRIVATE, x_shm_image_->bytes_per_line * x_shm_image_->height,
+ IPC_CREAT | 0600);
+ if (shm_segment_info_->shmid != -1) {
+ void* shmat_result = shmat(shm_segment_info_->shmid, 0, 0);
+ if (shmat_result != reinterpret_cast<void*>(-1)) {
+ shm_segment_info_->shmaddr = reinterpret_cast<char*>(shmat_result);
+ x_shm_image_->data = shm_segment_info_->shmaddr;
+
+ XErrorTrap error_trap(display_);
+ using_shm = XShmAttach(display_, shm_segment_info_);
+ XSync(display_, False);
+ if (error_trap.GetLastErrorAndDisable() != 0)
+ using_shm = false;
+ if (using_shm) {
+ RTC_LOG(LS_VERBOSE)
+ << "Using X shared memory segment " << shm_segment_info_->shmid;
+ }
+ }
+ } else {
+ RTC_LOG(LS_WARNING) << "Failed to get shared memory segment. "
+ "Performance may be degraded.";
+ }
+ }
+
+ if (!using_shm) {
+ RTC_LOG(LS_WARNING)
+ << "Not using shared memory. Performance may be degraded.";
+ ReleaseSharedMemorySegment();
+ return;
+ }
+
+ if (have_pixmaps)
+ have_pixmaps = InitPixmaps(default_depth);
+
+ shmctl(shm_segment_info_->shmid, IPC_RMID, 0);
+ shm_segment_info_->shmid = -1;
+
+ RTC_LOG(LS_VERBOSE) << "Using X shared memory extension v" << major << "."
+ << minor << " with" << (have_pixmaps ? "" : "out")
+ << " pixmaps.";
+}
+
+bool XServerPixelBuffer::InitPixmaps(int depth) {
+ if (XShmPixmapFormat(display_) != ZPixmap)
+ return false;
+
+ {
+ XErrorTrap error_trap(display_);
+ shm_pixmap_ = XShmCreatePixmap(
+ display_, window_, shm_segment_info_->shmaddr, shm_segment_info_,
+ window_rect_.width(), window_rect_.height(), depth);
+ XSync(display_, False);
+ if (error_trap.GetLastErrorAndDisable() != 0) {
+ // `shm_pixmap_` is not not valid because the request was not processed
+ // by the X Server, so zero it.
+ shm_pixmap_ = 0;
+ return false;
+ }
+ }
+
+ {
+ XErrorTrap error_trap(display_);
+ XGCValues shm_gc_values;
+ shm_gc_values.subwindow_mode = IncludeInferiors;
+ shm_gc_values.graphics_exposures = False;
+ shm_gc_ = XCreateGC(display_, window_,
+ GCSubwindowMode | GCGraphicsExposures, &shm_gc_values);
+ XSync(display_, False);
+ if (error_trap.GetLastErrorAndDisable() != 0) {
+ XFreePixmap(display_, shm_pixmap_);
+ shm_pixmap_ = 0;
+ shm_gc_ = 0; // See shm_pixmap_ comment above.
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool XServerPixelBuffer::IsWindowValid() const {
+ XWindowAttributes attributes;
+ {
+ XErrorTrap error_trap(display_);
+ if (!XGetWindowAttributes(display_, window_, &attributes) ||
+ error_trap.GetLastErrorAndDisable() != 0) {
+ return false;
+ }
+ }
+ return true;
+}
+
+void XServerPixelBuffer::Synchronize() {
+ if (shm_segment_info_ && !shm_pixmap_) {
+ // XShmGetImage can fail if the display is being reconfigured.
+ XErrorTrap error_trap(display_);
+ // XShmGetImage fails if the window is partially out of screen.
+ xshm_get_image_succeeded_ =
+ XShmGetImage(display_, window_, x_shm_image_, 0, 0, AllPlanes);
+ }
+}
+
+bool XServerPixelBuffer::CaptureRect(const DesktopRect& rect,
+ DesktopFrame* frame) {
+ RTC_DCHECK_LE(rect.right(), window_rect_.width());
+ RTC_DCHECK_LE(rect.bottom(), window_rect_.height());
+
+ XImage* image;
+ uint8_t* data;
+
+ if (shm_segment_info_ && (shm_pixmap_ || xshm_get_image_succeeded_)) {
+ if (shm_pixmap_) {
+ XCopyArea(display_, window_, shm_pixmap_, shm_gc_, rect.left(),
+ rect.top(), rect.width(), rect.height(), rect.left(),
+ rect.top());
+ XSync(display_, False);
+ }
+
+ image = x_shm_image_;
+ data = reinterpret_cast<uint8_t*>(image->data) +
+ rect.top() * image->bytes_per_line +
+ rect.left() * image->bits_per_pixel / 8;
+
+ } else {
+ if (x_image_)
+ XDestroyImage(x_image_);
+ x_image_ = XGetImage(display_, window_, rect.left(), rect.top(),
+ rect.width(), rect.height(), AllPlanes, ZPixmap);
+ if (!x_image_)
+ return false;
+
+ image = x_image_;
+ data = reinterpret_cast<uint8_t*>(image->data);
+ }
+
+ if (IsXImageRGBFormat(image)) {
+ FastBlit(image, data, rect, frame);
+ } else {
+ SlowBlit(image, data, rect, frame);
+ }
+
+ if (!icc_profile_.empty())
+ frame->set_icc_profile(icc_profile_);
+
+ return true;
+}
+
+} // namespace webrtc
diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_server_pixel_buffer.h b/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_server_pixel_buffer.h
new file mode 100644
index 0000000000..38af3a3e76
--- /dev/null
+++ b/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_server_pixel_buffer.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2013 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.
+ */
+
+// Don't include this file in any .h files because it pulls in some X headers.
+
+#ifndef MODULES_DESKTOP_CAPTURE_LINUX_X11_X_SERVER_PIXEL_BUFFER_H_
+#define MODULES_DESKTOP_CAPTURE_LINUX_X11_X_SERVER_PIXEL_BUFFER_H_
+
+#include <X11/Xutil.h>
+#include <X11/extensions/XShm.h>
+
+#include <memory>
+#include <vector>
+
+#include "modules/desktop_capture/desktop_geometry.h"
+
+namespace webrtc {
+
+class DesktopFrame;
+class XAtomCache;
+
+// A class to allow the X server's pixel buffer to be accessed as efficiently
+// as possible.
+class XServerPixelBuffer {
+ public:
+ XServerPixelBuffer();
+ ~XServerPixelBuffer();
+
+ XServerPixelBuffer(const XServerPixelBuffer&) = delete;
+ XServerPixelBuffer& operator=(const XServerPixelBuffer&) = delete;
+
+ void Release();
+
+ // Allocate (or reallocate) the pixel buffer for `window`. Returns false in
+ // case of an error (e.g. window doesn't exist).
+ bool Init(XAtomCache* cache, Window window);
+
+ bool is_initialized() { return window_ != 0; }
+
+ // Returns the size of the window the buffer was initialized for.
+ DesktopSize window_size() { return window_rect_.size(); }
+
+ // Returns the rectangle of the window the buffer was initialized for.
+ const DesktopRect& window_rect() { return window_rect_; }
+
+ // Returns true if the window can be found.
+ bool IsWindowValid() const;
+
+ // If shared memory is being used without pixmaps, synchronize this pixel
+ // buffer with the root window contents (otherwise, this is a no-op).
+ // This is to avoid doing a full-screen capture for each individual
+ // rectangle in the capture list, when it only needs to be done once at the
+ // beginning.
+ void Synchronize();
+
+ // Capture the specified rectangle and stores it in the `frame`. In the case
+ // where the full-screen data is captured by Synchronize(), this simply
+ // returns the pointer without doing any more work. The caller must ensure
+ // that `rect` is not larger than window_size().
+ bool CaptureRect(const DesktopRect& rect, DesktopFrame* frame);
+
+ private:
+ void ReleaseSharedMemorySegment();
+
+ void InitShm(const XWindowAttributes& attributes);
+ bool InitPixmaps(int depth);
+
+ Display* display_ = nullptr;
+ Window window_ = 0;
+ DesktopRect window_rect_;
+ XImage* x_image_ = nullptr;
+ XShmSegmentInfo* shm_segment_info_ = nullptr;
+ XImage* x_shm_image_ = nullptr;
+ Pixmap shm_pixmap_ = 0;
+ GC shm_gc_ = nullptr;
+ bool xshm_get_image_succeeded_ = false;
+ std::vector<uint8_t> icc_profile_;
+};
+
+} // namespace webrtc
+
+#endif // MODULES_DESKTOP_CAPTURE_LINUX_X11_X_SERVER_PIXEL_BUFFER_H_
diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_window_property.cc b/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_window_property.cc
new file mode 100644
index 0000000000..5e16dac404
--- /dev/null
+++ b/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_window_property.cc
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2019 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/x11/x_window_property.h"
+
+namespace webrtc {
+
+XWindowPropertyBase::XWindowPropertyBase(Display* display,
+ Window window,
+ Atom property,
+ int expected_size) {
+ const int kBitsPerByte = 8;
+ Atom actual_type;
+ int actual_format;
+ unsigned long bytes_after; // NOLINT: type required by XGetWindowProperty
+ int status = XGetWindowProperty(display, window, property, 0L, ~0L, False,
+ AnyPropertyType, &actual_type, &actual_format,
+ &size_, &bytes_after, &data_);
+ if (status != Success) {
+ data_ = nullptr;
+ return;
+ }
+ if ((expected_size * kBitsPerByte) != actual_format) {
+ size_ = 0;
+ return;
+ }
+
+ is_valid_ = true;
+}
+
+XWindowPropertyBase::~XWindowPropertyBase() {
+ if (data_)
+ XFree(data_);
+}
+
+} // namespace webrtc
diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_window_property.h b/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_window_property.h
new file mode 100644
index 0000000000..28dfb97311
--- /dev/null
+++ b/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_window_property.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2019 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_X11_X_WINDOW_PROPERTY_H_
+#define MODULES_DESKTOP_CAPTURE_LINUX_X11_X_WINDOW_PROPERTY_H_
+
+#include <X11/X.h>
+#include <X11/Xlib.h>
+
+namespace webrtc {
+
+class XWindowPropertyBase {
+ public:
+ XWindowPropertyBase(Display* display,
+ Window window,
+ Atom property,
+ int expected_size);
+ virtual ~XWindowPropertyBase();
+
+ XWindowPropertyBase(const XWindowPropertyBase&) = delete;
+ XWindowPropertyBase& operator=(const XWindowPropertyBase&) = delete;
+
+ // True if we got properly value successfully.
+ bool is_valid() const { return is_valid_; }
+
+ // Size and value of the property.
+ size_t size() const { return size_; }
+
+ protected:
+ unsigned char* data_ = nullptr;
+
+ private:
+ bool is_valid_ = false;
+ unsigned long size_ = 0; // NOLINT: type required by XGetWindowProperty
+};
+
+// Convenience wrapper for XGetWindowProperty() results.
+template <class PropertyType>
+class XWindowProperty : public XWindowPropertyBase {
+ public:
+ XWindowProperty(Display* display, const Window window, const Atom property)
+ : XWindowPropertyBase(display, window, property, sizeof(PropertyType)) {}
+ ~XWindowProperty() override = default;
+
+ XWindowProperty(const XWindowProperty&) = delete;
+ XWindowProperty& operator=(const XWindowProperty&) = delete;
+
+ const PropertyType* data() const {
+ return reinterpret_cast<PropertyType*>(data_);
+ }
+ PropertyType* data() { return reinterpret_cast<PropertyType*>(data_); }
+};
+
+} // namespace webrtc
+
+#endif // MODULES_DESKTOP_CAPTURE_LINUX_X11_X_WINDOW_PROPERTY_H_