diff options
Diffstat (limited to 'third_party/libwebrtc/modules/desktop_capture/linux/x11')
20 files changed, 2652 insertions, 0 deletions
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_ |