blob: 70c956d32e41484c332685d6ccaa4c9e75809b9f [file] [log] [blame]
// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "remoting/host/capturer_linux.h"
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/extensions/Xdamage.h>
#include <set>
#include "remoting/base/types.h"
namespace remoting {
static int IndexOfLowestBit(unsigned int mask) {
int i = 0;
// Extra-special do-while premature optimization, just to make dmaclach@
// happy.
do {
if (mask & 1) {
return i;
}
mask >>= 1;
++i;
} while (mask);
NOTREACHED() << "mask should never be 0.";
return 0;
}
static bool IsRgb32(XImage* image) {
return (IndexOfLowestBit(image->red_mask) == 16) &&
(IndexOfLowestBit(image->green_mask) == 8) &&
(IndexOfLowestBit(image->blue_mask) == 0);
}
static uint32_t* GetRowStart(uint8* buffer_start, int stride, int cur_row) {
return reinterpret_cast<uint32_t*>(buffer_start + (cur_row * stride));
}
// Private Implementation pattern to avoid leaking the X11 types into the header
// file.
class CapturerLinuxPimpl {
public:
explicit CapturerLinuxPimpl(CapturerLinux* capturer);
~CapturerLinuxPimpl();
bool Init(); // TODO(ajwong): Do we really want this to be synchronous?
void CalculateInvalidRects();
void CaptureRects(const InvalidRects& rects,
Capturer::CaptureCompletedCallback* callback);
private:
void DeinitXlib();
// We expose two forms of blitting to handle variations in the pixel format.
// In FastBlit, the operation is effectively a memcpy.
void FastBlit(XImage* image, int dest_x, int dest_y,
CaptureData* capture_data);
void SlowBlit(XImage* image, int dest_x, int dest_y,
CaptureData* capture_data);
static const int kBytesPerPixel = 4;
// Reference to containing class so we can access friend functions.
// Not owned.
CapturerLinux* capturer_;
// X11 graphics context.
Display* display_;
GC gc_;
Window root_window_;
// XDamage information.
Damage damage_handle_;
int damage_event_base_;
int damage_error_base_;
// Capture state.
uint8* buffers_[CapturerLinux::kNumBuffers];
int stride_;
bool capture_fullscreen_;
};
CapturerLinux::CapturerLinux(MessageLoop* message_loop)
: Capturer(message_loop),
pimpl_(new CapturerLinuxPimpl(this)) {
// TODO(ajwong): This should be moved into an Init() method on Capturer
// itself. Then we can remove the CHECK.
CHECK(pimpl_->Init());
}
CapturerLinux::~CapturerLinux() {
}
void CapturerLinux::ScreenConfigurationChanged() {
// TODO(ajwong): Support resolution changes.
NOTIMPLEMENTED();
}
void CapturerLinux::CalculateInvalidRects() {
pimpl_->CalculateInvalidRects();
}
void CapturerLinux::CaptureRects(const InvalidRects& rects,
CaptureCompletedCallback* callback) {
pimpl_->CaptureRects(rects, callback);
}
CapturerLinuxPimpl::CapturerLinuxPimpl(CapturerLinux* capturer)
: capturer_(capturer),
display_(NULL),
gc_(NULL),
root_window_(BadValue),
damage_handle_(BadValue),
damage_event_base_(-1),
damage_error_base_(-1),
stride_(0),
capture_fullscreen_(true) {
for (int i = 0; i < CapturerLinux::kNumBuffers; i++) {
buffers_[i] = NULL;
}
}
CapturerLinuxPimpl::~CapturerLinuxPimpl() {
DeinitXlib();
for (int i = 0; i < CapturerLinux::kNumBuffers; i++) {
delete [] buffers_[i];
buffers_[i] = NULL;
}
}
bool CapturerLinuxPimpl::Init() {
// TODO(ajwong): We should specify the display string we are attaching to
// in the constructor.
display_ = XOpenDisplay(NULL);
if (!display_) {
LOG(ERROR) << "Unable to open display";
return false;
}
root_window_ = RootWindow(display_, DefaultScreen(display_));
if (root_window_ == BadValue) {
LOG(ERROR) << "Unable to get the root window";
DeinitXlib();
return false;
}
gc_ = XCreateGC(display_, root_window_, 0, NULL);
if (gc_ == NULL) {
LOG(ERROR) << "Unable to get graphics context";
DeinitXlib();
return false;
}
// Setup XDamage to report changes in the damage window. Mark the whole
// window as invalid.
if (!XDamageQueryExtension(display_, &damage_event_base_,
&damage_error_base_)) {
LOG(ERROR) << "Server does not support XDamage.";
DeinitXlib();
return false;
}
damage_handle_ = XDamageCreate(display_, root_window_,
XDamageReportDeltaRectangles);
if (damage_handle_ == BadValue) {
LOG(ERROR) << "Unable to create damage handle.";
DeinitXlib();
return false;
}
// TODO(ajwong): We should be able to replace this with a XDamageAdd().
capture_fullscreen_ = true;
// Set up the dimensions of the catpure framebuffer.
XWindowAttributes root_attr;
XGetWindowAttributes(display_, root_window_, &root_attr);
capturer_->width_ = root_attr.width;
capturer_->height_ = root_attr.height;
stride_ = capturer_->width() * kBytesPerPixel;
VLOG(1) << "Initialized with Geometry: " << capturer_->width()
<< "x" << capturer_->height();
// Allocate the screen buffers.
for (int i = 0; i < CapturerLinux::kNumBuffers; i++) {
buffers_[i] =
new uint8[capturer_->width() * capturer_->height() * kBytesPerPixel];
}
return true;
}
void CapturerLinuxPimpl::CalculateInvalidRects() {
// TODO(ajwong): The capture_fullscreen_ logic here is very ugly. Refactor.
// 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;
InvalidRects invalid_rects;
for (int i = 0; i < events_to_process; i++) {
XNextEvent(display_, &e);
if (e.type == damage_event_base_ + XDamageNotify) {
// If we're doing a full screen capture, we should just drain the events.
if (!capture_fullscreen_) {
XDamageNotifyEvent *event = reinterpret_cast<XDamageNotifyEvent*>(&e);
gfx::Rect damage_rect(event->area.x, event->area.y, event->area.width,
event->area.height);
invalid_rects.insert(damage_rect);
VLOG(3) << "Damage receved for rect at ("
<< damage_rect.x() << "," << damage_rect.y() << ") size ("
<< damage_rect.width() << "," << damage_rect.height() << ")";
}
} else {
LOG(WARNING) << "Got unknown event type: " << e.type;
}
}
if (capture_fullscreen_) {
capturer_->InvalidateFullScreen();
capture_fullscreen_ = false;
} else {
capturer_->InvalidateRects(invalid_rects);
}
}
void CapturerLinuxPimpl::CaptureRects(
const InvalidRects& rects,
Capturer::CaptureCompletedCallback* callback) {
uint8* buffer = buffers_[capturer_->current_buffer_];
DataPlanes planes;
planes.data[0] = buffer;
planes.strides[0] = stride_;
scoped_refptr<CaptureData> capture_data(
new CaptureData(planes, capturer_->width(), capturer_->height(),
media::VideoFrame::RGB32));
for (InvalidRects::const_iterator it = rects.begin();
it != rects.end();
++it) {
XImage* image = XGetImage(display_, root_window_, it->x(), it->y(),
it->width(), it->height(), AllPlanes, ZPixmap);
// Check if we can fastpath the blit.
if ((image->depth == 24 || image->depth == 32) &&
image->bits_per_pixel == 32 &&
IsRgb32(image)) {
VLOG(3) << "Fast blitting";
FastBlit(image, it->x(), it->y(), capture_data);
} else {
VLOG(3) << "Slow blitting";
SlowBlit(image, it->x(), it->y(), capture_data);
}
XDestroyImage(image);
}
// TODO(ajwong): We should only repair the rects that were copied!
XDamageSubtract(display_, damage_handle_, None, None);
capture_data->mutable_dirty_rects() = rects;
// TODO(ajwong): These completion signals back to the upper class are very
// strange. Fix it.
capturer_->FinishCapture(capture_data, callback);
}
void CapturerLinuxPimpl::DeinitXlib() {
if (gc_) {
XFreeGC(display_, gc_);
gc_ = NULL;
}
if (display_) {
XCloseDisplay(display_);
display_ = NULL;
}
}
void CapturerLinuxPimpl::FastBlit(XImage* image, int dest_x, int dest_y,
CaptureData* capture_data) {
uint8* src_pos = reinterpret_cast<uint8*>(image->data);
DataPlanes planes = capture_data->data_planes();
uint8* dst_buffer = planes.data[0];
const int dst_stride = planes.strides[0];
const int src_stride = image->bytes_per_line;
// TODO(ajwong): I think this can never happen anyways due to the way we size
// the buffer. Check to be certain and possibly remove check.
CHECK((dst_stride - dest_x) >= src_stride);
// Flip the coordinate system to match the client.
for (int y = image->height - 1; y >= 0; y--) {
uint32_t* dst_pos = GetRowStart(dst_buffer, dst_stride, y + dest_y);
dst_pos += dest_x;
memcpy(dst_pos, src_pos, src_stride);
src_pos += src_stride;
}
}
void CapturerLinuxPimpl::SlowBlit(XImage* image, int dest_x, int dest_y,
CaptureData* capture_data) {
DataPlanes planes = capture_data->data_planes();
uint8* dst_buffer = planes.data[0];
const int dst_stride = planes.strides[0];
unsigned int red_shift = IndexOfLowestBit(image->red_mask);
unsigned int blue_shift = IndexOfLowestBit(image->blue_mask);
unsigned int green_shift = IndexOfLowestBit(image->green_mask);
unsigned int max_red = image->red_mask >> red_shift;
unsigned int max_blue = image->blue_mask >> blue_shift;
unsigned int max_green = image->green_mask >> green_shift;
for (int y = 0; y < image->height; y++) {
// Flip the coordinate system to match the client.
int dst_row = image->height - y - 1;
uint32_t* dst_pos = GetRowStart(dst_buffer, dst_stride, dst_row + dest_y);
dst_pos += dest_x;
for (int x = 0; x < image->width; x++) {
unsigned long pixel = XGetPixel(image, x, y);
uint32_t r = (((pixel & image->red_mask) >> red_shift) * max_red) / 255;
uint32_t g =
(((pixel & image->green_mask) >> green_shift) * max_blue) / 255;
uint32_t b =
(((pixel & image->blue_mask) >> blue_shift) * max_green) / 255;
// Write as 32-bit RGB.
*dst_pos = r << 16 | g << 8 | b;
dst_pos++;
}
}
}
// static
Capturer* Capturer::Create(MessageLoop* message_loop) {
return new CapturerLinux(message_loop);
}
} // namespace remoting