WindowCapturer implementation for Linux.

Window enumeration is based on the code used by hangouts plugin
(see libjingle/talk/base/linuxwindowpicker.cc). XServerPixelBuffer
is used to capture windows. It had to be refactored to support window
capturing (previously it worked only for the whole screen).

R=wez@chromium.org

Review URL: https://webrtc-codereview.appspot.com/1741004

git-svn-id: http://webrtc.googlecode.com/svn/trunk@4605 4adac7df-926f-26a2-2b94-8c16560cd09d
This commit is contained in:
sergeyu@chromium.org 2013-08-23 18:22:12 +00:00
parent 563910bde3
commit 9f282403f2
9 changed files with 707 additions and 309 deletions

View File

@ -53,9 +53,11 @@
"win/scoped_thread_desktop.cc",
"win/scoped_thread_desktop.h",
"window_capturer.h",
"window_capturer_linux.cc",
"window_capturer_mac.cc",
"window_capturer_win.cc",
"window_capturer_x11.cc",
"x11/x_error_trap.cc",
"x11/x_error_trap.h",
"x11/x_server_pixel_buffer.cc",
"x11/x_server_pixel_buffer.h",
],
@ -69,15 +71,18 @@
'link_settings': {
'libraries': [
'-lX11',
'-lXcomposite',
'-lXdamage',
'-lXext',
'-lXfixes',
'-lXrender',
],
},
}],
['OS!="win" and OS!="mac" and use_x11==0', {
'sources': [
"screen_capturer_null.cc",
"window_capturer_null.cc",
],
}],
['OS=="mac"', {

View File

@ -76,9 +76,8 @@ class ScreenCapturerLinux : public ScreenCapturer {
// differences between this and the previous capture.
DesktopFrame* CaptureScreen();
// Called when the screen configuration is changed. |root_window_size|
// specifies the most recent size of the root window.
void ScreenConfigurationChanged(const DesktopSize& root_window_size);
// 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|.
@ -89,25 +88,6 @@ class ScreenCapturerLinux : public ScreenCapturer {
void DeinitXlib();
// Capture a rectangle from |x_server_pixel_buffer_|, and copy the data into
// |frame|.
void CaptureRect(const DesktopRect& rect,
DesktopFrame* frame);
// We expose two forms of blitting to handle variations in the pixel format.
// In FastBlit, the operation is effectively a memcpy.
void FastBlit(uint8_t* image,
const DesktopRect& rect,
DesktopFrame* frame);
void SlowBlit(uint8_t* image,
const DesktopRect& rect,
DesktopFrame* frame);
// 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.
static uint32_t GetRgbShift(uint32_t mask);
Callback* callback_;
MouseShapeObserver* mouse_shape_observer_;
@ -116,9 +96,6 @@ class ScreenCapturerLinux : public ScreenCapturer {
GC gc_;
Window root_window_;
// Last known dimensions of the root window.
DesktopSize root_window_size_;
// XFixes.
bool has_xfixes_;
int xfixes_event_base_;
@ -207,8 +184,10 @@ bool ScreenCapturerLinux::Init(bool use_x_damage) {
// Register for changes to the dimensions of the root window.
XSelectInput(display_, root_window_, StructureNotifyMask);
root_window_size_ = XServerPixelBuffer::GetRootWindowSize(display_);
x_server_pixel_buffer_.Init(display_, root_window_size_);
if (!x_server_pixel_buffer_.Init(display_, DefaultRootWindow(display_))) {
LOG(LS_ERROR) << "Failed to initialize pixel buffer.";
return false;
}
if (has_xfixes_) {
// Register for changes to the cursor shape.
@ -276,12 +255,21 @@ void ScreenCapturerLinux::Capture(const DesktopRegion& region) {
// Process XEvents for XDamage and cursor shape tracking.
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.
callback_->OnCaptureCompleted(NULL);
return;
}
// If the current frame is from an older generation then allocate a new one.
// Note that we can't reallocate other buffers at this point, since the caller
// may still be reading from them.
if (!queue_.current_frame()) {
scoped_ptr<DesktopFrame> frame(
new BasicDesktopFrame(root_window_size_));
new BasicDesktopFrame(x_server_pixel_buffer_.window_size()));
queue_.ReplaceCurrentFrame(frame.release());
}
@ -324,9 +312,7 @@ void ScreenCapturerLinux::ProcessPendingXEvents() {
XDamageNotifyEvent* event = reinterpret_cast<XDamageNotifyEvent*>(&e);
DCHECK(event->level == XDamageReportNonEmpty);
} else if (e.type == ConfigureNotify) {
const XConfigureEvent& event = e.xconfigure;
ScreenConfigurationChanged(
DesktopSize(event.width, event.height));
ScreenConfigurationChanged();
} else if (has_xfixes_ &&
e.type == xfixes_event_base_ + XFixesCursorNotify) {
XFixesCursorNotifyEvent* cne;
@ -371,6 +357,7 @@ void ScreenCapturerLinux::CaptureCursor() {
DesktopFrame* ScreenCapturerLinux::CaptureScreen() {
DesktopFrame* frame = queue_.current_frame()->Share();
assert(x_server_pixel_buffer_.window_size().equals(frame->size()));
// Pass the screen size to the helper, so it can clip the invalid region if it
// expands that region to a grid.
@ -407,18 +394,17 @@ DesktopFrame* ScreenCapturerLinux::CaptureScreen() {
// spurious XDamage notifications were received for a previous (larger)
// screen size.
updated_region->IntersectWith(
DesktopRect::MakeSize(root_window_size_));
DesktopRect::MakeSize(x_server_pixel_buffer_.window_size()));
for (DesktopRegion::Iterator it(*updated_region);
!it.IsAtEnd(); it.Advance()) {
CaptureRect(it.rect(), frame);
x_server_pixel_buffer_.CaptureRect(it.rect(), frame);
}
} else {
// Doing full-screen polling, or this is the first capture after a
// screen-resolution change. In either case, need a full-screen capture.
DesktopRect screen_rect =
DesktopRect::MakeSize(frame->size());
CaptureRect(screen_rect, frame);
DesktopRect screen_rect = DesktopRect::MakeSize(frame->size());
x_server_pixel_buffer_.CaptureRect(screen_rect, frame);
if (queue_.previous_frame()) {
// Full-screen polling, so calculate the invalid rects here, based on the
@ -439,15 +425,15 @@ DesktopFrame* ScreenCapturerLinux::CaptureScreen() {
return frame;
}
void ScreenCapturerLinux::ScreenConfigurationChanged(
const DesktopSize& root_window_size) {
root_window_size_ = root_window_size;
void ScreenCapturerLinux::ScreenConfigurationChanged() {
// Make sure the frame buffers will be reallocated.
queue_.Reset();
helper_.ClearInvalidRegion();
x_server_pixel_buffer_.Init(display_, root_window_size_);
if (!x_server_pixel_buffer_.Init(display_, DefaultRootWindow(display_))) {
LOG(LS_ERROR) << "Failed to initialize pixel buffer after screen "
"configuration change.";
}
}
void ScreenCapturerLinux::SynchronizeFrame() {
@ -497,114 +483,6 @@ void ScreenCapturerLinux::DeinitXlib() {
}
}
void ScreenCapturerLinux::CaptureRect(const DesktopRect& rect,
DesktopFrame* frame) {
uint8_t* image = x_server_pixel_buffer_.CaptureRect(rect);
int depth = x_server_pixel_buffer_.GetDepth();
if ((depth == 24 || depth == 32) &&
x_server_pixel_buffer_.GetBitsPerPixel() == 32 &&
x_server_pixel_buffer_.GetRedMask() == 0xff0000 &&
x_server_pixel_buffer_.GetGreenMask() == 0xff00 &&
x_server_pixel_buffer_.GetBlueMask() == 0xff) {
FastBlit(image, rect, frame);
} else {
SlowBlit(image, rect, frame);
}
}
void ScreenCapturerLinux::FastBlit(uint8_t* image,
const DesktopRect& rect,
DesktopFrame* frame) {
uint8_t* src_pos = image;
int src_stride = x_server_pixel_buffer_.GetStride();
int dst_x = rect.left(), dst_y = rect.top();
uint8_t* dst_pos = frame->data() + frame->stride() * dst_y;
dst_pos += dst_x * DesktopFrame::kBytesPerPixel;
int height = rect.height();
int row_bytes = rect.width() * DesktopFrame::kBytesPerPixel;
for (int y = 0; y < height; ++y) {
memcpy(dst_pos, src_pos, row_bytes);
src_pos += src_stride;
dst_pos += frame->stride();
}
}
void ScreenCapturerLinux::SlowBlit(uint8_t* image,
const DesktopRect& rect,
DesktopFrame* frame) {
int src_stride = x_server_pixel_buffer_.GetStride();
int dst_x = rect.left(), dst_y = rect.top();
int width = rect.width(), height = rect.height();
uint32_t red_mask = x_server_pixel_buffer_.GetRedMask();
uint32_t green_mask = x_server_pixel_buffer_.GetGreenMask();
uint32_t blue_mask = x_server_pixel_buffer_.GetBlueMask();
uint32_t red_shift = GetRgbShift(red_mask);
uint32_t green_shift = GetRgbShift(green_mask);
uint32_t blue_shift = GetRgbShift(blue_mask);
unsigned int bits_per_pixel = x_server_pixel_buffer_.GetBitsPerPixel();
uint8_t* dst_pos = frame->data() + frame->stride() * dst_y;
uint8_t* src_pos = image;
dst_pos += dst_x * DesktopFrame::kBytesPerPixel;
// TODO(hclam): Optimize, perhaps using MMX code or by converting to
// YUV directly
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;
}
}
// static
uint32_t ScreenCapturerLinux::GetRgbShift(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;
}
} // namespace
// static

View File

@ -18,10 +18,10 @@ namespace webrtc {
namespace {
class WindowCapturerLinux : public WindowCapturer {
class WindowCapturerNull : public WindowCapturer {
public:
WindowCapturerLinux();
virtual ~WindowCapturerLinux();
WindowCapturerNull();
virtual ~WindowCapturerNull();
// WindowCapturer interface.
virtual bool GetWindowList(WindowList* windows) OVERRIDE;
@ -34,34 +34,34 @@ class WindowCapturerLinux : public WindowCapturer {
private:
Callback* callback_;
DISALLOW_COPY_AND_ASSIGN(WindowCapturerLinux);
DISALLOW_COPY_AND_ASSIGN(WindowCapturerNull);
};
WindowCapturerLinux::WindowCapturerLinux()
WindowCapturerNull::WindowCapturerNull()
: callback_(NULL) {
}
WindowCapturerLinux::~WindowCapturerLinux() {
WindowCapturerNull::~WindowCapturerNull() {
}
bool WindowCapturerLinux::GetWindowList(WindowList* windows) {
bool WindowCapturerNull::GetWindowList(WindowList* windows) {
// Not implemented yet.
return false;
}
bool WindowCapturerLinux::SelectWindow(WindowId id) {
bool WindowCapturerNull::SelectWindow(WindowId id) {
// Not implemented yet.
return false;
}
void WindowCapturerLinux::Start(Callback* callback) {
void WindowCapturerNull::Start(Callback* callback) {
assert(!callback_);
assert(callback);
callback_ = callback;
}
void WindowCapturerLinux::Capture(const DesktopRegion& region) {
void WindowCapturerNull::Capture(const DesktopRegion& region) {
// Not implemented yet.
callback_->OnCaptureCompleted(NULL);
}
@ -70,7 +70,7 @@ void WindowCapturerLinux::Capture(const DesktopRegion& region) {
// static
WindowCapturer* WindowCapturer::Create() {
return new WindowCapturerLinux();
return new WindowCapturerNull();
}
} // namespace webrtc

View File

@ -10,6 +10,8 @@
#include "webrtc/modules/desktop_capture/window_capturer.h"
#include <iostream>
#include "gtest/gtest.h"
#include "webrtc/modules/desktop_capture/desktop_frame.h"
#include "webrtc/modules/desktop_capture/desktop_region.h"
@ -42,8 +44,6 @@ class WindowCapturerTest : public testing::Test,
scoped_ptr<DesktopFrame> frame_;
};
#if defined(WEBRTC_WIN) || defined(WEBRTC_MAC)
// Verify that we can enumerate windows.
TEST_F(WindowCapturerTest, Enumerate) {
WindowCapturer::WindowList windows;
@ -92,6 +92,4 @@ TEST_F(WindowCapturerTest, Capture) {
}
}
#endif // defined(WEBRTC_WIN) || defined(WEBRTC_MAC)
} // namespace webrtc

View File

@ -0,0 +1,354 @@
/*
* 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 "webrtc/modules/desktop_capture/window_capturer.h"
#include <string.h>
#include <X11/Xatom.h>
#include <X11/extensions/Xcomposite.h>
#include <X11/extensions/Xrender.h>
#include <X11/Xutil.h>
#include <algorithm>
#include <cassert>
#include "webrtc/modules/desktop_capture/desktop_frame.h"
#include "webrtc/modules/desktop_capture/x11/x_error_trap.h"
#include "webrtc/modules/desktop_capture/x11/x_server_pixel_buffer.h"
#include "webrtc/system_wrappers/interface/logging.h"
#include "webrtc/system_wrappers/interface/scoped_ptr.h"
namespace webrtc {
namespace {
// Convenience wrapper for XGetWindowProperty() results.
template <class PropertyType>
class XWindowProperty {
public:
XWindowProperty(Display* display, Window window, Atom property)
: is_valid_(false),
size_(0),
data_(NULL) {
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_ = NULL;
return;
}
if (sizeof(PropertyType) * kBitsPerByte != actual_format) {
size_ = 0;
return;
}
is_valid_ = true;
}
~XWindowProperty() {
if (data_)
XFree(data_);
}
// 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_; }
const PropertyType* data() const {
return reinterpret_cast<PropertyType*>(data_);
}
PropertyType* data() {
return reinterpret_cast<PropertyType*>(data_);
}
private:
bool is_valid_;
unsigned long size_; // NOLINT: type required by XGetWindowProperty
unsigned char* data_;
DISALLOW_COPY_AND_ASSIGN(XWindowProperty);
};
class WindowCapturerLinux : public WindowCapturer {
public:
WindowCapturerLinux();
virtual ~WindowCapturerLinux();
// WindowCapturer interface.
virtual bool GetWindowList(WindowList* windows) OVERRIDE;
virtual bool SelectWindow(WindowId id) OVERRIDE;
// DesktopCapturer interface.
virtual void Start(Callback* callback) OVERRIDE;
virtual void Capture(const DesktopRegion& region) OVERRIDE;
private:
// 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(::Window window);
// Returns true if the |window| is a desktop element.
bool IsDesktopElement(::Window window);
// Returns window title for the specified X |window|.
bool GetWindowTitle(::Window window, std::string* title);
Callback* callback_;
Display* display_;
Atom wm_state_atom_;
Atom window_type_atom_;
Atom normal_window_type_atom_;
bool has_composite_extension_;
::Window selected_window_;
XServerPixelBuffer x_server_pixel_buffer_;
DISALLOW_COPY_AND_ASSIGN(WindowCapturerLinux);
};
WindowCapturerLinux::WindowCapturerLinux()
: callback_(NULL),
display_(NULL),
has_composite_extension_(false),
selected_window_(0) {
display_ = XOpenDisplay(NULL);
if (!display_) {
LOG(LS_ERROR) << "Failed to open display.";
return;
}
// Create Atoms so we don't need to do it every time they are used.
wm_state_atom_ = XInternAtom(display_, "WM_STATE", True);
window_type_atom_ = XInternAtom(display_, "_NET_WM_WINDOW_TYPE", True);
normal_window_type_atom_ = XInternAtom(
display_, "_NET_WM_WINDOW_TYPE_NORMAL", True);
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 {
LOG(LS_INFO) << "Xcomposite extension not available or too old.";
}
}
WindowCapturerLinux::~WindowCapturerLinux() {
if (display_)
XCloseDisplay(display_);
}
bool WindowCapturerLinux::GetWindowList(WindowList* windows) {
if (!display_)
return false;
WindowList result;
XErrorTrap error_trap(display_);
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;
int status = XQueryTree(display_, root_window, &root_window, &parent,
&children, &num_children);
if (status == 0) {
LOG(LS_ERROR) << "Failed to query for child windows for screen "
<< screen;
continue;
}
for (unsigned int i = 0; i < num_children; ++i) {
// Iterate in reverse order to return windows from front to back.
::Window app_window =
GetApplicationWindow(children[num_children - 1 - i]);
if (app_window && !IsDesktopElement(app_window)) {
Window w;
w.id = app_window;
if (GetWindowTitle(app_window, &w.title))
result.push_back(w);
}
}
if (children)
XFree(children);
}
windows->swap(result);
return true;
}
bool WindowCapturerLinux::SelectWindow(WindowId id) {
if (!x_server_pixel_buffer_.Init(display_, id))
return false;
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;
}
void WindowCapturerLinux::Start(Callback* callback) {
assert(!callback_);
assert(callback);
callback_ = callback;
}
void WindowCapturerLinux::Capture(const DesktopRegion& region) {
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.
LOG(LS_INFO) << "No Xcomposite extension detected.";
callback_->OnCaptureCompleted(NULL);
return;
}
DesktopFrame* frame =
new BasicDesktopFrame(x_server_pixel_buffer_.window_size());
x_server_pixel_buffer_.Synchronize();
x_server_pixel_buffer_.CaptureRect(DesktopRect::MakeSize(frame->size()),
frame);
callback_->OnCaptureCompleted(frame);
}
::Window WindowCapturerLinux::GetApplicationWindow(::Window window) {
// Get WM_STATE property of the window.
XWindowProperty<uint32_t> window_state(display_, window, wm_state_atom_);
// WM_STATE is considered to be set to WithdrawnState when it missing.
int32_t state = window_state.is_valid() ?
*window_state.data() : WithdrawnState;
if (state == NormalState) {
// Window has WM_STATE==NormalState. Return it.
return window;
} else if (state == IconicState) {
// Window is in minimized. Skip it.
return 0;
}
// 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)) {
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(children[i]);
if (app_window)
break;
}
if (children)
XFree(children);
return app_window;
}
bool WindowCapturerLinux::IsDesktopElement(::Window window) {
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(display_, window, window_type_atom_);
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, normal_window_type_atom_));
return !is_normal;
}
// Fall back on using the hint.
XClassHint class_hint;
Status status = XGetClassHint(display_, window, &class_hint);
bool result = false;
if (status == 0) {
// No hints, assume this is a normal application window.
return result;
}
if (strcmp("gnome-panel", class_hint.res_name) == 0 ||
strcmp("desktop_window", class_hint.res_name) == 0) {
result = true;
}
XFree(class_hint.res_name);
XFree(class_hint.res_class);
return result;
}
bool WindowCapturerLinux::GetWindowTitle(::Window window, std::string* title) {
int status;
bool result = false;
XTextProperty window_name;
window_name.value = NULL;
if (window) {
status = XGetWMName(display_, window, &window_name);
if (status && window_name.value && window_name.nitems) {
int cnt;
char **list = NULL;
status = Xutf8TextPropertyToTextList(display_, &window_name, &list,
&cnt);
if (status >= Success && cnt && *list) {
if (cnt > 1) {
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;
}
} // namespace
// static
WindowCapturer* WindowCapturer::Create() {
return new WindowCapturerLinux();
}
} // namespace webrtc

View File

@ -0,0 +1,69 @@
/*
* 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 "webrtc/modules/desktop_capture/x11/x_error_trap.h"
#include <assert.h>
#if defined(TOOLKIT_GTK)
#include <gdk/gdk.h>
#endif // !defined(TOOLKIT_GTK)
namespace webrtc {
namespace {
#if !defined(TOOLKIT_GTK)
// TODO(sergeyu): This code is not thread safe. Fix it. Bug 2202.
static bool g_xserver_error_trap_enabled = false;
static int g_last_xserver_error_code = 0;
int XServerErrorHandler(Display* display, XErrorEvent* error_event) {
assert(g_xserver_error_trap_enabled);
g_last_xserver_error_code = error_event->error_code;
return 0;
}
#endif // !defined(TOOLKIT_GTK)
} // namespace
XErrorTrap::XErrorTrap(Display* display)
: original_error_handler_(NULL),
enabled_(true) {
#if defined(TOOLKIT_GTK)
gdk_error_trap_push();
#else // !defined(TOOLKIT_GTK)
assert(!g_xserver_error_trap_enabled);
original_error_handler_ = XSetErrorHandler(&XServerErrorHandler);
g_xserver_error_trap_enabled = true;
g_last_xserver_error_code = 0;
#endif // !defined(TOOLKIT_GTK)
}
int XErrorTrap::GetLastErrorAndDisable() {
enabled_ = false;
#if defined(TOOLKIT_GTK)
return gdk_error_trap_push();
#else // !defined(TOOLKIT_GTK)
assert(g_xserver_error_trap_enabled);
XSetErrorHandler(original_error_handler_);
g_xserver_error_trap_enabled = false;
return g_last_xserver_error_code;
#endif // !defined(TOOLKIT_GTK)
}
XErrorTrap::~XErrorTrap() {
if (enabled_)
GetLastErrorAndDisable();
}
} // namespace webrtc

View File

@ -0,0 +1,39 @@
/*
* 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 WEBRTC_MODULES_DESKTOP_CAPTURE_X11_X_ERROR_TRAP_H_
#define WEBRTC_MODULES_DESKTOP_CAPTURE_X11_X_ERROR_TRAP_H_
#include <X11/Xlib.h>
#include "webrtc/system_wrappers/interface/constructor_magic.h"
namespace webrtc {
// Helper class that registers 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();
// Returns last error and removes unregisters the error handler.
int GetLastErrorAndDisable();
private:
XErrorHandler original_error_handler_;
bool enabled_;
DISALLOW_COPY_AND_ASSIGN(XErrorTrap);
};
} // namespace webrtc
#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_X11_X_ERROR_TRAP_H_

View File

@ -11,62 +11,56 @@
#include "webrtc/modules/desktop_capture/x11/x_server_pixel_buffer.h"
#include <assert.h>
#include <string.h>
#include <sys/shm.h>
#include "webrtc/modules/desktop_capture/desktop_frame.h"
#include "webrtc/modules/desktop_capture/x11/x_error_trap.h"
#include "webrtc/system_wrappers/interface/logging.h"
#if defined(TOOLKIT_GTK)
#include <gdk/gdk.h>
#else // !defined(TOOLKIT_GTK)
#include <X11/Xlib.h>
#endif // !defined(TOOLKIT_GTK)
namespace {
#if defined(TOOLKIT_GTK)
// GDK sets error handler for Xlib errors, so we need to use it to
// trap X errors when this code is compiled with GTK.
void EnableXServerErrorTrap() {
gdk_error_trap_push();
// 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;
}
int GetLastXServerError() {
return gdk_error_trap_pop();
// 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;
}
#else // !defined(TOOLKIT_GTK)
static bool g_xserver_error_trap_enabled = false;
static int g_last_xserver_error_code = 0;
int XServerErrorHandler(Display* display, XErrorEvent* error_event) {
assert(g_xserver_error_trap_enabled);
g_last_xserver_error_code = error_event->error_code;
return 0;
}
void EnableXServerErrorTrap() {
assert(!g_xserver_error_trap_enabled);
XSetErrorHandler(&XServerErrorHandler);
g_xserver_error_trap_enabled = true;
g_last_xserver_error_code = 0;
}
int GetLastXServerError() {
assert(g_xserver_error_trap_enabled);
XSetErrorHandler(NULL);
g_xserver_error_trap_enabled = false;
return g_last_xserver_error_code;
}
#endif // !defined(TOOLKIT_GTK)
} // namespace
namespace webrtc {
XServerPixelBuffer::XServerPixelBuffer()
: display_(NULL), root_window_(0),
: display_(NULL), window_(0),
x_image_(NULL),
shm_segment_info_(NULL), shm_pixmap_(0), shm_gc_(NULL) {
}
@ -96,34 +90,39 @@ void XServerPixelBuffer::Release() {
delete shm_segment_info_;
shm_segment_info_ = NULL;
}
window_ = 0;
}
void XServerPixelBuffer::Init(Display* display,
const DesktopSize& screen_size) {
bool XServerPixelBuffer::Init(Display* display, Window window) {
Release();
display_ = display;
root_window_size_ = screen_size;
int default_screen = DefaultScreen(display_);
root_window_ = RootWindow(display_, default_screen);
InitShm(default_screen);
XWindowAttributes attributes;
{
XErrorTrap error_trap(display_);
if (!XGetWindowAttributes(display_, window, &attributes) ||
error_trap.GetLastErrorAndDisable() != 0) {
return false;
}
}
window_size_ = DesktopSize(attributes.width, attributes.height);
window_ = window;
InitShm(attributes);
return true;
}
// static
DesktopSize XServerPixelBuffer::GetRootWindowSize(Display* display) {
XWindowAttributes root_attr;
XGetWindowAttributes(display, DefaultRootWindow(display), &root_attr);
return DesktopSize(root_attr.width, root_attr.height);
}
void XServerPixelBuffer::InitShm(int screen) {
Visual* default_visual = DefaultVisual(display_, screen);
int default_depth = DefaultDepth(display_, screen);
void XServerPixelBuffer::InitShm(const XWindowAttributes& attributes) {
Visual* default_visual = attributes.visual;
int default_depth = attributes.depth;
int major, minor;
Bool havePixmaps;
if (!XShmQueryVersion(display_, &major, &minor, &havePixmaps))
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;
@ -131,8 +130,8 @@ void XServerPixelBuffer::InitShm(int screen) {
shm_segment_info_->shmaddr = reinterpret_cast<char*>(-1);
shm_segment_info_->readOnly = False;
x_image_ = XShmCreateImage(display_, default_visual, default_depth, ZPixmap,
0, shm_segment_info_, root_window_size_.width(),
root_window_size_.height());
0, shm_segment_info_, window_size_.width(),
window_size_.height());
if (x_image_) {
shm_segment_info_->shmid = shmget(
IPC_PRIVATE, x_image_->bytes_per_line * x_image_->height,
@ -141,10 +140,10 @@ void XServerPixelBuffer::InitShm(int screen) {
shm_segment_info_->shmaddr = x_image_->data =
reinterpret_cast<char*>(shmat(shm_segment_info_->shmid, 0, 0));
if (x_image_->data != reinterpret_cast<char*>(-1)) {
EnableXServerErrorTrap();
XErrorTrap error_trap(display_);
using_shm = XShmAttach(display_, shm_segment_info_);
XSync(display_, False);
if (GetLastXServerError() != 0)
if (error_trap.GetLastErrorAndDisable() != 0)
using_shm = false;
if (using_shm) {
LOG(LS_VERBOSE) << "Using X shared memory segment "
@ -163,48 +162,52 @@ void XServerPixelBuffer::InitShm(int screen) {
return;
}
if (havePixmaps)
havePixmaps = InitPixmaps(default_depth);
if (have_pixmaps)
have_pixmaps = InitPixmaps(default_depth);
shmctl(shm_segment_info_->shmid, IPC_RMID, 0);
shm_segment_info_->shmid = -1;
LOG(LS_VERBOSE) << "Using X shared memory extension v"
<< major << "." << minor
<< " with" << (havePixmaps ? "" : "out") << " pixmaps.";
<< " with" << (have_pixmaps ? "" : "out") << " pixmaps.";
}
bool XServerPixelBuffer::InitPixmaps(int depth) {
if (XShmPixmapFormat(display_) != ZPixmap)
return false;
EnableXServerErrorTrap();
shm_pixmap_ = XShmCreatePixmap(display_, root_window_,
shm_segment_info_->shmaddr,
shm_segment_info_,
root_window_size_.width(),
root_window_size_.height(), depth);
XSync(display_, False);
if (GetLastXServerError() != 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_);
shm_pixmap_ = XShmCreatePixmap(display_, window_,
shm_segment_info_->shmaddr,
shm_segment_info_,
window_size_.width(),
window_size_.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;
}
}
EnableXServerErrorTrap();
XGCValues shm_gc_values;
shm_gc_values.subwindow_mode = IncludeInferiors;
shm_gc_values.graphics_exposures = False;
shm_gc_ = XCreateGC(display_, root_window_,
GCSubwindowMode | GCGraphicsExposures,
&shm_gc_values);
XSync(display_, False);
if (GetLastXServerError() != 0) {
XFreePixmap(display_, shm_pixmap_);
shm_pixmap_ = 0;
shm_gc_ = 0; // See shm_pixmap_ comment above.
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;
@ -213,57 +216,110 @@ bool XServerPixelBuffer::InitPixmaps(int depth) {
void XServerPixelBuffer::Synchronize() {
if (shm_segment_info_ && !shm_pixmap_) {
// XShmGetImage can fail if the display is being reconfigured.
EnableXServerErrorTrap();
XShmGetImage(display_, root_window_, x_image_, 0, 0, AllPlanes);
GetLastXServerError();
XErrorTrap error_trap(display_);
XShmGetImage(display_, window_, x_image_, 0, 0, AllPlanes);
}
}
uint8_t* XServerPixelBuffer::CaptureRect(const DesktopRect& rect) {
assert(rect.right() <= root_window_size_.width());
assert(rect.bottom() <= root_window_size_.height());
void XServerPixelBuffer::CaptureRect(const DesktopRect& rect,
DesktopFrame* frame) {
assert(rect.right() <= window_size_.width());
assert(rect.bottom() <= window_size_.height());
uint8_t* data;
if (shm_segment_info_) {
if (shm_pixmap_) {
XCopyArea(display_, root_window_, shm_pixmap_, shm_gc_,
XCopyArea(display_, window_, shm_pixmap_, shm_gc_,
rect.left(), rect.top(), rect.width(), rect.height(),
rect.left(), rect.top());
XSync(display_, False);
}
return reinterpret_cast<uint8_t*>(x_image_->data) +
data = reinterpret_cast<uint8_t*>(x_image_->data) +
rect.top() * x_image_->bytes_per_line +
rect.left() * x_image_->bits_per_pixel / 8;
} else {
if (x_image_)
XDestroyImage(x_image_);
x_image_ = XGetImage(display_, root_window_, rect.left(), rect.top(),
x_image_ = XGetImage(display_, window_, rect.left(), rect.top(),
rect.width(), rect.height(), AllPlanes, ZPixmap);
return reinterpret_cast<uint8_t*>(x_image_->data);
data = reinterpret_cast<uint8_t*>(x_image_->data);
}
if (IsXImageRGBFormat(x_image_)) {
FastBlit(data, rect, frame);
} else {
SlowBlit(data, rect, frame);
}
}
int XServerPixelBuffer::GetStride() const {
return x_image_->bytes_per_line;
void XServerPixelBuffer::FastBlit(uint8_t* image,
const DesktopRect& rect,
DesktopFrame* frame) {
uint8_t* src_pos = image;
int src_stride = x_image_->bytes_per_line;
int dst_x = rect.left(), dst_y = rect.top();
uint8_t* dst_pos = frame->data() + frame->stride() * dst_y;
dst_pos += dst_x * DesktopFrame::kBytesPerPixel;
int height = rect.height();
int row_bytes = rect.width() * DesktopFrame::kBytesPerPixel;
for (int y = 0; y < height; ++y) {
memcpy(dst_pos, src_pos, row_bytes);
src_pos += src_stride;
dst_pos += frame->stride();
}
}
int XServerPixelBuffer::GetDepth() const {
return x_image_->depth;
}
void XServerPixelBuffer::SlowBlit(uint8_t* image,
const DesktopRect& rect,
DesktopFrame* frame) {
int src_stride = x_image_->bytes_per_line;
int dst_x = rect.left(), dst_y = rect.top();
int width = rect.width(), height = rect.height();
int XServerPixelBuffer::GetBitsPerPixel() const {
return x_image_->bits_per_pixel;
}
uint32_t red_mask = x_image_->red_mask;
uint32_t green_mask = x_image_->red_mask;
uint32_t blue_mask = x_image_->blue_mask;
int XServerPixelBuffer::GetRedMask() const {
return x_image_->red_mask;
}
uint32_t red_shift = MaskToShift(red_mask);
uint32_t green_shift = MaskToShift(green_mask);
uint32_t blue_shift = MaskToShift(blue_mask);
int XServerPixelBuffer::GetBlueMask() const {
return x_image_->blue_mask;
}
int bits_per_pixel = x_image_->bits_per_pixel;
int XServerPixelBuffer::GetGreenMask() const {
return x_image_->green_mask;
uint8_t* dst_pos = frame->data() + frame->stride() * dst_y;
uint8_t* src_pos = image;
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 webrtc

View File

@ -20,6 +20,8 @@
namespace webrtc {
class DesktopFrame;
// A class to allow the X server's pixel buffer to be accessed as efficiently
// as possible.
class XServerPixelBuffer {
@ -29,14 +31,14 @@ class XServerPixelBuffer {
void Release();
// Allocate (or reallocate) the pixel buffer with the given size, which is
// assumed to be the current size of the root window.
// |screen_size| should either come from GetRootWindowSize(), or
// from a recent ConfigureNotify event on the root window.
void Init(Display* display, const DesktopSize& screen_size);
// Allocate (or reallocate) the pixel buffer for |window|. Returns false in
// case of an error (e.g. window doesn't exist).
bool Init(Display* display, Window window);
// Request the current size of the root window from the X Server.
static DesktopSize GetRootWindowSize(Display* display);
bool is_initialized() { return window_ != 0; }
// Returns the size of the window the buffer was initialized for.
const DesktopSize& window_size() { return window_size_; }
// If shared memory is being used without pixmaps, synchronize this pixel
// buffer with the root window contents (otherwise, this is a no-op).
@ -45,31 +47,28 @@ class XServerPixelBuffer {
// beginning.
void Synchronize();
// Capture the specified rectangle and return a pointer to its top-left pixel
// or NULL if capture fails. The returned pointer remains valid until the next
// call to CaptureRect.
// 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 no larger than the screen size
// supplied to Init().
uint8_t* CaptureRect(const DesktopRect& rect);
// Return information about the most recent capture. This is only guaranteed
// to be valid between CaptureRect calls.
int GetStride() const;
int GetDepth() const;
int GetBitsPerPixel() const;
int GetRedMask() const;
int GetBlueMask() const;
int GetGreenMask() const;
// 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().
void CaptureRect(const DesktopRect& rect, DesktopFrame* frame);
private:
void InitShm(int screen);
void InitShm(const XWindowAttributes& attributes);
bool InitPixmaps(int depth);
// We expose two forms of blitting to handle variations in the pixel format.
// In FastBlit(), the operation is effectively a memcpy.
void FastBlit(uint8_t* image,
const DesktopRect& rect,
DesktopFrame* frame);
void SlowBlit(uint8_t* image,
const DesktopRect& rect,
DesktopFrame* frame);
Display* display_;
Window root_window_;
DesktopSize root_window_size_;
Window window_;
DesktopSize window_size_;
XImage* x_image_;
XShmSegmentInfo* shm_segment_info_;
Pixmap shm_pixmap_;