diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:47:29 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:47:29 +0000 |
commit | 0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d (patch) | |
tree | a31f07c9bcca9d56ce61e9a1ffd30ef350d513aa /third_party/libwebrtc/modules/desktop_capture/linux | |
parent | Initial commit. (diff) | |
download | firefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.tar.xz firefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.zip |
Adding upstream version 115.8.0esr.upstream/115.8.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/libwebrtc/modules/desktop_capture/linux')
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_ |