Repo created
This commit is contained in:
parent
81b91f4139
commit
f8c34fa5ee
22732 changed files with 4815320 additions and 2 deletions
|
|
@ -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/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) {
|
||||
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 once per instance of MouseCursorMonitor.
|
||||
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.
|
||||
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
|
||||
|
|
@ -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_
|
||||
|
|
@ -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), 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
|
||||
|
|
@ -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_
|
||||
|
|
@ -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
|
||||
|
|
@ -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_
|
||||
|
|
@ -0,0 +1,248 @@
|
|||
/*
|
||||
* 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;
|
||||
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");
|
||||
|
||||
if (!x_server_pixel_buffer_.IsWindowValid()) {
|
||||
RTC_LOG(LS_ERROR) << "The window is no longer valid.";
|
||||
callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
x_display_->ProcessPendingXEvents();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// 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
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* 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/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);
|
||||
|
||||
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_
|
||||
|
|
@ -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
|
||||
|
|
@ -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_
|
||||
|
|
@ -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
|
||||
|
|
@ -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_
|
||||
|
|
@ -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
|
||||
|
|
@ -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_
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* 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 <atomic>
|
||||
|
||||
#include "rtc_base/checks.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
namespace {
|
||||
|
||||
static int g_last_xserver_error_code = 0;
|
||||
static std::atomic<Display*> g_display_for_error_handler = nullptr;
|
||||
|
||||
Mutex* AcquireMutex() {
|
||||
static Mutex* mutex = new Mutex();
|
||||
return mutex;
|
||||
}
|
||||
|
||||
int XServerErrorHandler(Display* display, XErrorEvent* error_event) {
|
||||
RTC_DCHECK_EQ(display, g_display_for_error_handler.load());
|
||||
g_last_xserver_error_code = error_event->error_code;
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
XErrorTrap::XErrorTrap(Display* display) : mutex_lock_(AcquireMutex()) {
|
||||
// We don't expect this class to be used in a nested fashion so therefore
|
||||
// g_display_for_error_handler should never be valid here.
|
||||
RTC_DCHECK(!g_display_for_error_handler.load());
|
||||
RTC_DCHECK(display);
|
||||
g_display_for_error_handler.store(display);
|
||||
g_last_xserver_error_code = 0;
|
||||
original_error_handler_ = XSetErrorHandler(&XServerErrorHandler);
|
||||
}
|
||||
|
||||
int XErrorTrap::GetLastErrorAndDisable() {
|
||||
g_display_for_error_handler.store(nullptr);
|
||||
XSetErrorHandler(original_error_handler_);
|
||||
return g_last_xserver_error_code;
|
||||
}
|
||||
|
||||
XErrorTrap::~XErrorTrap() {
|
||||
if (g_display_for_error_handler.load() != nullptr)
|
||||
GetLastErrorAndDisable();
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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/Xlib.h>
|
||||
|
||||
#include "rtc_base/synchronization/mutex.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// Helper class that registers an X Window error handler. Caller can use
|
||||
// GetLastErrorAndDisable() to get the last error that was caught, if any.
|
||||
class XErrorTrap {
|
||||
public:
|
||||
explicit XErrorTrap(Display* display);
|
||||
|
||||
XErrorTrap(const XErrorTrap&) = delete;
|
||||
XErrorTrap& operator=(const XErrorTrap&) = delete;
|
||||
|
||||
~XErrorTrap();
|
||||
|
||||
// Returns the last error if one was caught, otherwise 0. Also unregisters the
|
||||
// error handler and replaces it with `original_error_handler_`.
|
||||
int GetLastErrorAndDisable();
|
||||
|
||||
private:
|
||||
MutexLock mutex_lock_;
|
||||
XErrorHandler original_error_handler_ = nullptr;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_DESKTOP_CAPTURE_LINUX_X11_X_ERROR_TRAP_H_
|
||||
|
|
@ -0,0 +1,370 @@
|
|||
/*
|
||||
* 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());
|
||||
|
||||
frame->CopyPixelsFrom(
|
||||
src_pos, x_image->bytes_per_line,
|
||||
DesktopRect::MakeXYWH(rect.left() - frame->top_left().x(),
|
||||
rect.top() - frame->top_left().y(), rect.width(),
|
||||
rect.height()));
|
||||
}
|
||||
|
||||
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
|
||||
|
|
@ -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_
|
||||
|
|
@ -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
|
||||
|
|
@ -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_
|
||||
Loading…
Add table
Add a link
Reference in a new issue