diff options
author | ajwong@chromium.org <ajwong@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-10-21 20:05:01 +0000 |
---|---|---|
committer | ajwong@chromium.org <ajwong@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-10-21 20:05:01 +0000 |
commit | 66980d85fefe26261b310f35375a31bb91abd0c3 (patch) | |
tree | 43298b3d634c9e7cc0b887b12f4ba42957d10253 /remoting | |
parent | 9d0b0fbfc061a1889ae18fbefa92f7b73b065750 (diff) | |
download | chromium_src-66980d85fefe26261b310f35375a31bb91abd0c3.zip chromium_src-66980d85fefe26261b310f35375a31bb91abd0c3.tar.gz chromium_src-66980d85fefe26261b310f35375a31bb91abd0c3.tar.bz2 |
Implement basic X11 screen capture on linux.
BUG=
TEST=
Review URL: http://codereview.chromium.org/3801012
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@63401 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'remoting')
-rw-r--r-- | remoting/base/encoder_zlib.cc | 3 | ||||
-rw-r--r-- | remoting/client/jingle_host_connection.cc | 2 | ||||
-rw-r--r-- | remoting/host/capturer_linux.cc | 318 | ||||
-rw-r--r-- | remoting/host/capturer_linux.h | 4 | ||||
-rw-r--r-- | remoting/host/chromoting_host.cc | 2 | ||||
-rw-r--r-- | remoting/remoting.gyp | 6 |
6 files changed, 332 insertions, 3 deletions
diff --git a/remoting/base/encoder_zlib.cc b/remoting/base/encoder_zlib.cc index a36b8df..ddbe923 100644 --- a/remoting/base/encoder_zlib.cc +++ b/remoting/base/encoder_zlib.cc @@ -27,7 +27,8 @@ void EncoderZlib::Encode(scoped_refptr<CaptureData> capture_data, bool key_frame, DataAvailableCallback* data_available_callback) { CHECK(capture_data->pixel_format() == PixelFormatRgb32) - << "Zlib Encoder only works with RGB32"; + << "Zlib Encoder only works with RGB32. Got " + << capture_data->pixel_format(); capture_data_ = capture_data; callback_.reset(data_available_callback); diff --git a/remoting/client/jingle_host_connection.cc b/remoting/client/jingle_host_connection.cc index f40f720..c70e2c6 100644 --- a/remoting/client/jingle_host_connection.cc +++ b/remoting/client/jingle_host_connection.cc @@ -65,6 +65,8 @@ void JingleHostConnection::InitConnection() { // Initialize |chromotocol_server_|. JingleChromotingServer* chromotocol_server = new JingleChromotingServer(message_loop()); + // TODO(ajwong): Make this a command switch when we're more stable. + chromotocol_server->set_allow_local_ips(true); chromotocol_server->Init( jingle_client_->GetFullJid(), jingle_client_->session_manager(), diff --git a/remoting/host/capturer_linux.cc b/remoting/host/capturer_linux.cc index e53c597..fcf7a01 100644 --- a/remoting/host/capturer_linux.cc +++ b/remoting/host/capturer_linux.cc @@ -4,23 +4,337 @@ #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 { -// TODO(dmaclach): Implement this class. -CapturerLinux::CapturerLinux() { +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. + 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() + : 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(), + PixelFormatRgb32)); + + 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++; + } + } } } // namespace remoting diff --git a/remoting/host/capturer_linux.h b/remoting/host/capturer_linux.h index b7c2d93..8d7b32d 100644 --- a/remoting/host/capturer_linux.h +++ b/remoting/host/capturer_linux.h @@ -9,6 +9,8 @@ namespace remoting { +class CapturerLinuxPimpl; + // A class to perform capturing for Linux. class CapturerLinux : public Capturer { public: @@ -18,10 +20,12 @@ class CapturerLinux : public Capturer { virtual void ScreenConfigurationChanged(); private: + friend class CapturerLinuxPimpl; virtual void CalculateInvalidRects(); virtual void CaptureRects(const InvalidRects& rects, CaptureCompletedCallback* callback); + scoped_ptr<CapturerLinuxPimpl> pimpl_; DISALLOW_COPY_AND_ASSIGN(CapturerLinux); }; diff --git a/remoting/host/chromoting_host.cc b/remoting/host/chromoting_host.cc index d28ca79..7676eda 100644 --- a/remoting/host/chromoting_host.cc +++ b/remoting/host/chromoting_host.cc @@ -217,6 +217,8 @@ void ChromotingHost::OnStateChange(JingleClient* jingle_client, // Create and start |chromotocol_server_|. chromotocol_server_ = new JingleChromotingServer(context_->jingle_thread()->message_loop()); + // TODO(ajwong): Make this a command switch when we're more stable. + chromotocol_server_->set_allow_local_ips(true); chromotocol_server_->Init( jingle_client->GetFullJid(), jingle_client->session_manager(), diff --git a/remoting/remoting.gyp b/remoting/remoting.gyp index 0dc310d..72ed762f 100644 --- a/remoting/remoting.gyp +++ b/remoting/remoting.gyp @@ -219,6 +219,12 @@ 'host/event_executor_linux.cc', 'host/event_executor_linux.h', ], + 'link_settings': { + 'libraries': [ + '-lX11', + '-lXdamage', + ], + }, }], ['OS=="mac"', { 'sources': [ |