diff options
Diffstat (limited to 'remoting/host')
43 files changed, 3756 insertions, 0 deletions
diff --git a/remoting/host/capturer.cc b/remoting/host/capturer.cc new file mode 100644 index 0000000..2f92d18 --- /dev/null +++ b/remoting/host/capturer.cc @@ -0,0 +1,49 @@ +// 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.h" + +namespace remoting { + +Capturer::Capturer() + : width_(0), + height_(0), + pixel_format_(chromotocol_pb::PixelFormatInvalid), + bytes_per_pixel_(0), + bytes_per_row_(0), + current_buffer_(0) { +} + +Capturer::~Capturer() { +} + +void Capturer::GetDirtyRects(DirtyRects* rects) const { + *rects = dirty_rects_; +} + +int Capturer::GetWidth() const { + return width_; +} + +int Capturer::GetHeight() const { + return height_; +} + +chromotocol_pb::PixelFormat Capturer::GetPixelFormat() const { + return pixel_format_; +} + +void Capturer::InvalidateRect(gfx::Rect dirty_rect) { + inval_rects_.push_back(dirty_rect); +} + +void Capturer::FinishCapture(Task* done_task) { + done_task->Run(); + delete done_task; + + // Select the next buffer to be the current buffer. + current_buffer_ = ++current_buffer_ % kNumBuffers; +} + +} // namespace remoting diff --git a/remoting/host/capturer.h b/remoting/host/capturer.h new file mode 100644 index 0000000..64e25f2 --- /dev/null +++ b/remoting/host/capturer.h @@ -0,0 +1,123 @@ +// 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. + +#ifndef REMOTING_HOST_CAPTURER_H_ +#define REMOTING_HOST_CAPTURER_H_ + +#include <vector> + +#include "base/basictypes.h" +#include "base/task.h" +#include "gfx/rect.h" +#include "remoting/base/protocol/chromotocol.pb.h" + +namespace remoting { + +typedef std::vector<gfx::Rect> DirtyRects; + +// A class to perform the task of capturing the image of a window. +// The capture action is asynchronous to allow maximum throughput. +// +// Implementation has to ensure the following gurantees: +// 1. Double buffering +// Since data can be read while another capture action is +// happening. +class Capturer { + public: + Capturer(); + virtual ~Capturer(); + + // Capture the full screen. When the action is completed |done_task| + // is called. + // + // It is OK to call this methods while another thread is reading + // data of the last capture. + // There can be at most one concurrent read going on when this + // methods is called. + virtual void CaptureFullScreen(Task* done_task) = 0; + + // Capture the updated regions since last capture. If the last + // capture doesn't exist, the full window is captured. + // + // When complete |done_task| is called. + // + // It is OK to call this method while another thread is reading + // data of the last capture. + // There can be at most one concurrent read going on when this + // methods is called. + virtual void CaptureDirtyRects(Task* done_task) = 0; + + // Capture the specified screen rect and call |done_task| when complete. + // Dirty or invalid regions are ignored and only the given |rect| area is + // captured. + // + // It is OK to call this method while another thread is reading + // data of the last capture. + // There can be at most one concurrent read going on when this + // methods is called. + virtual void CaptureRect(const gfx::Rect& rect, Task* done_task) = 0; + + // Get the image data of the last capture. The pointers to data is + // written to |planes|. |planes| should be an array of 3 elements. + virtual void GetData(const uint8* planes[]) const = 0; + + // Get the image data stride of the last capture. This size of strides + // is written to |strides|. |strides| should be array of 3 elements. + virtual void GetDataStride(int strides[]) const = 0; + + // Get the list of updated rectangles in the last capture. The result is + // written into |rects|. + virtual void GetDirtyRects(DirtyRects* rects) const; + + // Get the width of the image captured. + virtual int GetWidth() const; + + // Get the height of the image captured. + virtual int GetHeight() const; + + // Get the pixel format of the image captured. + virtual chromotocol_pb::PixelFormat GetPixelFormat() const; + + // Invalidate the specified screen rect. + virtual void InvalidateRect(gfx::Rect dirty); + + protected: + // Finish/cleanup capture task. + // This should be called at the end of each of the CaptureXxx() routines. + // This routine should (at least): + // (1) Call the |done_task| routine. + // (2) Select the next screen buffer. + // Note that capturers are required to be double-buffered so that we can + // read from one which capturing into another. + virtual void FinishCapture(Task* done_task); + + // Number of screen buffers. + static const int kNumBuffers = 2; + + // Capture screen dimensions. + int width_; + int height_; + + // Format of pixels returned in buffer. + chromotocol_pb::PixelFormat pixel_format_; + + // Information about screen. + int bytes_per_pixel_; + int bytes_per_row_; + + // The current buffer with valid data for reading. + int current_buffer_; + + // List of dirty rects. + // These are the rects that we send to the client to update. + DirtyRects dirty_rects_; + + // Rects that have been manually invalidated (through InvalidateRect). + // These will be merged into |dirty_rects_| during the next capture. + DirtyRects inval_rects_; +}; + +} // namespace remoting + +#endif // REMOTING_HOST_CAPTURER_H_ diff --git a/remoting/host/capturer_fake.cc b/remoting/host/capturer_fake.cc new file mode 100644 index 0000000..5c087b39 --- /dev/null +++ b/remoting/host/capturer_fake.cc @@ -0,0 +1,86 @@ +// 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_fake.h" + +#include "gfx/rect.h" + +namespace remoting { + +static const int kWidth = 640; +static const int kHeight = 480; +static const int kBytesPerPixel = 3; // 24 bit RGB is 3 bytes per pixel. +static const int kMaxColorChannelValue = 255; + +CapturerFake::CapturerFake() + : seed_(0) { + // Dimensions of screen. + width_ = kWidth; + height_ = kHeight; + pixel_format_ = chromotocol_pb::PixelFormatRgb24; + bytes_per_pixel_ = kBytesPerPixel; + bytes_per_row_ = width_ * bytes_per_pixel_; + + // Create memory for the buffers. + int buffer_size = height_ * bytes_per_row_; + for (int i = 0; i < kNumBuffers; i++) { + buffers_[i].reset(new uint8[buffer_size]); + } +} + +CapturerFake::~CapturerFake() { +} + +void CapturerFake::CaptureFullScreen(Task* done_task) { + dirty_rects_.clear(); + + GenerateImage(); + dirty_rects_.push_back(gfx::Rect(width_, height_)); + + FinishCapture(done_task); +} + +void CapturerFake::CaptureDirtyRects(Task* done_task) { + dirty_rects_.clear(); + + GenerateImage(); + // TODO(garykac): Diff old/new images and generate |dirty_rects_|. + // Currently, this just marks the entire screen as dirty. + dirty_rects_.push_back(gfx::Rect(width_, height_)); + + FinishCapture(done_task); +} + +void CapturerFake::CaptureRect(const gfx::Rect& rect, Task* done_task) { + dirty_rects_.clear(); + + GenerateImage(); + dirty_rects_.push_back(rect); + + FinishCapture(done_task); +} + +void CapturerFake::GetData(const uint8* planes[]) const { + planes[0] = buffers_[current_buffer_].get(); + planes[1] = planes[2] = NULL; +} + +void CapturerFake::GetDataStride(int strides[]) const { + // Only the first plane has data. + strides[0] = bytes_per_row_; + strides[1] = strides[2] = 0; +} + +void CapturerFake::GenerateImage() { + uint8* row = buffers_[current_buffer_].get(); + for (int y = 0; y < height_; ++y) { + for (int x = 0; x < width_; ++x) { + row[x] = seed_++; + seed_ &= kMaxColorChannelValue; + } + row += bytes_per_row_; + } +} + +} // namespace remoting diff --git a/remoting/host/capturer_fake.h b/remoting/host/capturer_fake.h new file mode 100644 index 0000000..e2b5282 --- /dev/null +++ b/remoting/host/capturer_fake.h @@ -0,0 +1,44 @@ +// 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. + +#ifndef REMOTING_HOST_CAPTURER_FAKE_H_ +#define REMOTING_HOST_CAPTURER_FAKE_H_ + +#include "base/scoped_ptr.h" +#include "remoting/host/capturer.h" + +namespace remoting { + +// A CapturerFake always output an image of 640x480 in 24bit RGB. The image +// is artificially generated for testing purpose. +// +// CapturerFake is doubled buffered as required by Capturer. See +// remoting/host/capturer.h. +class CapturerFake : public Capturer { + public: + CapturerFake(); + virtual ~CapturerFake(); + + virtual void CaptureFullScreen(Task* done_task); + virtual void CaptureDirtyRects(Task* done_task); + virtual void CaptureRect(const gfx::Rect& rect, Task* done_task); + virtual void GetData(const uint8* planes[]) const; + virtual void GetDataStride(int strides[]) const; + + private: + // Generates an image in the front buffer. + void GenerateImage(); + + // The seed for generating the image. + int seed_; + + // We have two buffers for the screen images as required by Capturer. + scoped_array<uint8> buffers_[kNumBuffers]; + + DISALLOW_COPY_AND_ASSIGN(CapturerFake); +}; + +} // namespace remoting + +#endif // REMOTING_HOST_CAPTURER_FAKE_H_ diff --git a/remoting/host/capturer_fake_ascii.cc b/remoting/host/capturer_fake_ascii.cc new file mode 100644 index 0000000..7ab7e6b --- /dev/null +++ b/remoting/host/capturer_fake_ascii.cc @@ -0,0 +1,88 @@ +// 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_fake_ascii.h" + +#include "gfx/rect.h" + +namespace remoting { + +static const int kWidth = 32; +static const int kHeight = 20; +static const int kBytesPerPixel = 1; + +CapturerFakeAscii::CapturerFakeAscii() { + // Dimensions of screen. + width_ = kWidth; + height_ = kHeight; + pixel_format_ = chromotocol_pb::PixelFormatAscii; + bytes_per_pixel_ = kBytesPerPixel; + bytes_per_row_ = width_ * bytes_per_pixel_; + + // Create memory for the buffers. + int buffer_size = height_ * bytes_per_row_; + for (int i = 0; i < kNumBuffers; i++) { + buffers_[i].reset(new uint8[buffer_size]); + } +} + +CapturerFakeAscii::~CapturerFakeAscii() { +} + +void CapturerFakeAscii::CaptureFullScreen(Task* done_task) { + dirty_rects_.clear(); + + GenerateImage(); + + // Return a single dirty rect that includes the entire screen. + dirty_rects_.push_back(gfx::Rect(width_, height_)); + + FinishCapture(done_task); +} + +void CapturerFakeAscii::CaptureDirtyRects(Task* done_task) { + dirty_rects_.clear(); + + GenerateImage(); + // TODO(garykac): Diff old/new screen. + // Currently, this just marks the entire screen as dirty. + dirty_rects_.push_back(gfx::Rect(width_, height_)); + + FinishCapture(done_task); +} + +void CapturerFakeAscii::CaptureRect(const gfx::Rect& rect, Task* done_task) { + dirty_rects_.clear(); + + GenerateImage(); + dirty_rects_.push_back(rect); + + FinishCapture(done_task); +} + +void CapturerFakeAscii::GetData(const uint8* planes[]) const { + planes[0] = buffers_[current_buffer_].get(); + planes[1] = planes[2] = NULL; +} + +void CapturerFakeAscii::GetDataStride(int strides[]) const { + // Only the first plane has data. + strides[0] = bytes_per_row_; + strides[1] = strides[2] = 0; +} + +void CapturerFakeAscii::GenerateImage() { + for (int y = 0; y < height_; ++y) { + uint8* row = buffers_[current_buffer_].get() + bytes_per_row_ * y; + for (int x = 0; x < bytes_per_row_; ++x) { + if (y == 0 || x == 0 || x == (width_ - 1) || y == (height_ - 1)) { + row[x] = '*'; + } else { + row[x] = ' '; + } + } + } +} + +} // namespace remoting diff --git a/remoting/host/capturer_fake_ascii.h b/remoting/host/capturer_fake_ascii.h new file mode 100644 index 0000000..ceb406b --- /dev/null +++ b/remoting/host/capturer_fake_ascii.h @@ -0,0 +1,41 @@ +// 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. + +#ifndef REMOTING_HOST_CAPTURER_FAKE_ASCII_H_ +#define REMOTING_HOST_CAPTURER_FAKE_ASCII_H_ + +#include "base/scoped_ptr.h" +#include "remoting/host/capturer.h" + +namespace remoting { + +// A CapturerFakeAscii always outputs an image of 64x48 ASCII characters. +// This image is artificially generated for testing purpose. +// +// CapturerFakeAscii is doubled buffered as required by Capturer. See +// remoting/host/capturer.h. +class CapturerFakeAscii : public Capturer { + public: + CapturerFakeAscii(); + virtual ~CapturerFakeAscii(); + + virtual void CaptureFullScreen(Task* done_task); + virtual void CaptureDirtyRects(Task* done_task); + virtual void CaptureRect(const gfx::Rect& rect, Task* done_task); + virtual void GetData(const uint8* planes[]) const; + virtual void GetDataStride(int strides[]) const; + + private: + // Generates an image in the front buffer. + void GenerateImage(); + + // We have two buffers for the screen images as required by Capturer. + scoped_array<uint8> buffers_[kNumBuffers]; + + DISALLOW_COPY_AND_ASSIGN(CapturerFakeAscii); +}; + +} // namespace remoting + +#endif // REMOTING_HOST_CAPTURER_FAKE_ASCII_H_ diff --git a/remoting/host/capturer_gdi.cc b/remoting/host/capturer_gdi.cc new file mode 100644 index 0000000..45e5224 --- /dev/null +++ b/remoting/host/capturer_gdi.cc @@ -0,0 +1,128 @@ +// 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_gdi.h" + +#include "gfx/rect.h" + +namespace remoting { + +// 3780 pixels per meter is equivalent to 96 DPI, typical on desktop monitors. +static const int kPixelsPerMeter = 3780; +// 24 bit RGB is 3 bytes per pixel. +static const int kBytesPerPixel = 3; + +CapturerGdi::CapturerGdi() + : initialized_(false) { +} + +CapturerGdi::~CapturerGdi() { + if (initialized_) { + for (int i = kNumBuffers - 1; i >= 0; i--) { + DeleteObject(target_bitmap_[i]); + } + } +} + +void CapturerGdi::CaptureFullScreen(Task* done_task) { + dirty_rects_.clear(); + + CaptureImage(); + dirty_rects_.push_back(gfx::Rect(width_, height_)); + + FinishCapture(done_task); +} + +void CapturerGdi::CaptureDirtyRects(Task* done_task) { + dirty_rects_.clear(); + + CaptureImage(); + // TODO(garykac): Diff old/new images and generate |dirty_rects_|. + // Currently, this just marks the entire screen as dirty. + dirty_rects_.push_back(gfx::Rect(width_, height_)); + + FinishCapture(done_task); +} + +void CapturerGdi::CaptureRect(const gfx::Rect& rect, Task* done_task) { + dirty_rects_.clear(); + + CaptureImage(); + dirty_rects_.push_back(rect); + + FinishCapture(done_task); +} + +void CapturerGdi::GetData(const uint8* planes[]) const { + planes[0] = static_cast<const uint8*>(buffers_[current_buffer_]); + planes[1] = planes[2] = NULL; +} + +void CapturerGdi::GetDataStride(int strides[]) const { + // Only the first plane has data. + strides[0] = bytes_per_row_; + strides[1] = strides[2] = 0; +} + +int CapturerGdi::GetWidth() const { + if (!width_) + width_ = GetSystemMetrics(SM_CXSCREEN); + return width_; +} + +int CapturerGdi::GetHeight() const { + if (!height_) + height_ = GetSystemMetrics(SM_CYSCREEN); + return height_; +} + +// TODO(fbarchard): handle error cases. +void CapturerGdi::InitializeBuffers() { + desktop_dc_ = GetDC(GetDesktopWindow()); + memory_dc_ = CreateCompatibleDC(desktop_dc_); + + // Create a bitmap to keep the desktop image. + width_ = GetSystemMetrics(SM_CXSCREEN); + height_ = GetSystemMetrics(SM_CYSCREEN); + int rounded_width = (width_ + 3) & (~3); + + // Dimensions of screen. + pixel_format_ = chromotocol_pb::PixelFormatRgb24; + bytes_per_pixel_ = kBytesPerPixel; + bytes_per_row_ = rounded_width * bytes_per_pixel_; + + // Create a device independant bitmap (DIB) that is the same size. + BITMAPINFO bmi; + memset(&bmi, 0, sizeof(bmi)); + bmi.bmiHeader.biHeight = height_; + bmi.bmiHeader.biWidth = width_; + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = bytes_per_pixel_ * 8; + bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader); + bmi.bmiHeader.biSizeImage = bytes_per_row_ * height_; + bmi.bmiHeader.biXPelsPerMeter = kPixelsPerMeter; + bmi.bmiHeader.biYPelsPerMeter = kPixelsPerMeter; + + // Create memory for the buffers. + for (int i = 0; i < kNumBuffers; i++) { + target_bitmap_[i] = CreateDIBSection(desktop_dc_, &bmi, DIB_RGB_COLORS, + static_cast<void**>(&buffers_[i]), + NULL, 0); + } + initialized_ = true; +} + +void CapturerGdi::CaptureImage() { + if (initialized_ == false) { + InitializeBuffers(); + } + // Selection the target bitmap into the memory dc. + SelectObject(memory_dc_, target_bitmap_[current_buffer_]); + + // And then copy the rect from desktop to memory. + BitBlt(memory_dc_, 0, 0, width_, height_, desktop_dc_, 0, 0, + SRCCOPY | CAPTUREBLT); +} + +} // namespace remoting diff --git a/remoting/host/capturer_gdi.h b/remoting/host/capturer_gdi.h new file mode 100644 index 0000000..7ccda6c --- /dev/null +++ b/remoting/host/capturer_gdi.h @@ -0,0 +1,52 @@ +// 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. + +#ifndef REMOTING_HOST_CAPTURER_GDI_H_ +#define REMOTING_HOST_CAPTURER_GDI_H_ + +#include <windows.h> +typedef HBITMAP BitmapRef; +#include "base/scoped_ptr.h" +#include "remoting/host/capturer.h" + +namespace remoting { + +// CapturerGdi captures 24bit RGB using GDI. +// +// CapturerGdi is doubled buffered as required by Capturer. See +// remoting/host/capturer.h. +class CapturerGdi : public Capturer { + public: + CapturerGdi(); + virtual ~CapturerGdi(); + + virtual void CaptureFullScreen(Task* done_task); + virtual void CaptureDirtyRects(Task* done_task); + virtual void CaptureRect(const gfx::Rect& rect, Task* done_task); + virtual void GetData(const uint8* planes[]) const; + virtual void GetDataStride(int strides[]) const; + virtual void GetWidth() const; + virtual void GetHeight() const; + + private: + // Initialize GDI structures. + void InitializeBuffers(); + // Generates an image in the current buffer. + void CaptureImage(); + + // Gdi specific information about screen. + HDC desktop_dc_; + HDC memory_dc_; + HBITMAP target_bitmap_[kNumBuffers]; + + // We have two buffers for the screen images as required by Capturer. + void* buffers_[kNumBuffers]; + bool initialized_; // Set to 'true' if buffers are initialized. + + DISALLOW_COPY_AND_ASSIGN(CapturerGdi); +}; + +} // namespace remoting + +#endif // REMOTING_HOST_CAPTURER_GDI_H_ diff --git a/remoting/host/capturer_gdi_unittest.cc b/remoting/host/capturer_gdi_unittest.cc new file mode 100644 index 0000000..850a4a6 --- /dev/null +++ b/remoting/host/capturer_gdi_unittest.cc @@ -0,0 +1,13 @@ +// 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 "testing/gmock/include/gmock/gmock.h" + +namespace remoting { + +TEST(CapturerGdiTest, Capture) { + // TODO(hclam): implement this. +} + +} // namespace remoting diff --git a/remoting/host/capturer_linux.cc b/remoting/host/capturer_linux.cc new file mode 100644 index 0000000..f5c89ee --- /dev/null +++ b/remoting/host/capturer_linux.cc @@ -0,0 +1,54 @@ +// 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" + +namespace remoting { + +// TODO(dmaclach): Implement this class. +CapturerLinux::CapturerLinux() { +} + +CapturerLinux::~CapturerLinux() { +} + +void CapturerLinux::CaptureFullScreen(Task* done_task) { + dirty_rects_.clear(); + + CaptureImage(); + dirty_rects_.push_back(gfx::Rect(width_, height_)); + + FinishCapture(done_task); +} + +void CapturerLinux::CaptureDirtyRects(Task* done_task) { + dirty_rects_.clear(); + + CaptureImage(); + // TODO(garykac): Diff old/new images and generate |dirty_rects_|. + // Currently, this just marks the entire screen as dirty. + dirty_rects_.push_back(gfx::Rect(width_, height_)); + + FinishCapture(done_task); +} + +void CapturerLinux::CaptureRect(const gfx::Rect& rect, Task* done_task) { + dirty_rects_.clear(); + + CaptureImage(); + dirty_rects_.push_back(rect); + + FinishCapture(done_task); +} + +void CapturerLinux::GetData(const uint8* planes[]) const { +} + +void CapturerLinux::GetDataStride(int strides[]) const { +} + +void CapturerLinux::CaptureImage() { +} + +} // namespace remoting diff --git a/remoting/host/capturer_linux.h b/remoting/host/capturer_linux.h new file mode 100644 index 0000000..1b9acb4 --- /dev/null +++ b/remoting/host/capturer_linux.h @@ -0,0 +1,33 @@ +// 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. + +#ifndef REMOTING_HOST_CAPTURER_LINUX_H_ +#define REMOTING_HOST_CAPTURER_LINUX_H_ + +#include "remoting/host/capturer.h" + +namespace remoting { + +// A class to perform capturing for Linux. +class CapturerLinux : public Capturer { + public: + CapturerLinux(); + virtual ~CapturerLinux(); + + virtual void CaptureFullScreen(Task* done_task); + virtual void CaptureDirtyRects(Task* done_task); + virtual void CaptureRect(const gfx::Rect& rect, Task* done_task); + virtual void GetData(const uint8* planes[]) const; + virtual void GetDataStride(int strides[]) const; + + private: + // Generates an image in the current buffer. + void CaptureImage(); + + DISALLOW_COPY_AND_ASSIGN(CapturerLinux); +}; + +} // namespace remoting + +#endif // REMOTING_HOST_CAPTURER_LINUX_H_ diff --git a/remoting/host/capturer_linux_unittest.cc b/remoting/host/capturer_linux_unittest.cc new file mode 100644 index 0000000..7b94b56 --- /dev/null +++ b/remoting/host/capturer_linux_unittest.cc @@ -0,0 +1,13 @@ +// 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 "testing/gmock/include/gmock/gmock.h" + +namespace remoting { + +TEST(CapturerLinuxTest, Capture) { + // TODO(dmaclach): implement this. +} + +} // namespace remoting diff --git a/remoting/host/capturer_mac.cc b/remoting/host/capturer_mac.cc new file mode 100644 index 0000000..08f39c7 --- /dev/null +++ b/remoting/host/capturer_mac.cc @@ -0,0 +1,54 @@ +// 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_mac.h" + +namespace remoting { + +// TODO(dmaclach): Implement this class. +CapturerMac::CapturerMac() { +} + +CapturerMac::~CapturerMac() { +} + +void CapturerMac::CaptureFullScreen(Task* done_task) { + dirty_rects_.clear(); + + CaptureImage(); + dirty_rects_.push_back(gfx::Rect(width_, height_)); + + FinishCapture(done_task); +} + +void CapturerMac::CaptureDirtyRects(Task* done_task) { + dirty_rects_.clear(); + + CaptureImage(); + // TODO(garykac): Diff old/new images and generate |dirty_rects_|. + // Currently, this just marks the entire screen as dirty. + dirty_rects_.push_back(gfx::Rect(width_, height_)); + + FinishCapture(done_task); +} + +void CapturerMac::CaptureRect(const gfx::Rect& rect, Task* done_task) { + dirty_rects_.clear(); + + CaptureImage(); + dirty_rects_.push_back(rect); + + FinishCapture(done_task); +} + +void CapturerMac::GetData(const uint8* planes[]) const { +} + +void CapturerMac::GetDataStride(int strides[]) const { +} + +void CapturerMac::CaptureImage() { +} + +} // namespace remoting diff --git a/remoting/host/capturer_mac.h b/remoting/host/capturer_mac.h new file mode 100644 index 0000000..da4073e --- /dev/null +++ b/remoting/host/capturer_mac.h @@ -0,0 +1,33 @@ +// 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. + +#ifndef REMOTING_HOST_CAPTURER_MAC_H_ +#define REMOTING_HOST_CAPTURER_MAC_H_ + +#include "remoting/host/capturer.h" + +namespace remoting { + +// A class to perform capturing for mac. +class CapturerMac : public Capturer { + public: + CapturerMac(); + virtual ~CapturerMac(); + + virtual void CaptureFullScreen(Task* done_task); + virtual void CaptureDirtyRects(Task* done_task); + virtual void CaptureRect(const gfx::Rect& rect, Task* done_task); + virtual void GetData(const uint8* planes[]) const; + virtual void GetDataStride(int strides[]) const; + + private: + // Generates an image in the current buffer. + void CaptureImage(); + + DISALLOW_COPY_AND_ASSIGN(CapturerMac); +}; + +} // namespace remoting + +#endif // REMOTING_HOST_CAPTURER_MAC_H_ diff --git a/remoting/host/capturer_mac_unittest.cc b/remoting/host/capturer_mac_unittest.cc new file mode 100644 index 0000000..1202861 --- /dev/null +++ b/remoting/host/capturer_mac_unittest.cc @@ -0,0 +1,13 @@ +// 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 "testing/gmock/include/gmock/gmock.h" + +namespace remoting { + +TEST(CapturerMacTest, Capture) { + // TODO(dmaclach): implement this. +} + +} // namespace remoting diff --git a/remoting/host/client_connection.cc b/remoting/host/client_connection.cc new file mode 100644 index 0000000..9ae7b95 --- /dev/null +++ b/remoting/host/client_connection.cc @@ -0,0 +1,173 @@ +// 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/client_connection.h" + +#include "google/protobuf/message.h" +#include "media/base/data_buffer.h" +#include "remoting/base/protocol_decoder.h" +#include "remoting/base/protocol_util.h" + +using media::DataBuffer; + +namespace remoting { + +// Determine how many update streams we should count to find the size of +// average update stream. +static const int kAverageUpdateStream = 10; + +ClientConnection::ClientConnection(MessageLoop* message_loop, + ProtocolDecoder* decoder, + EventHandler* handler) + : loop_(message_loop), + decoder_(decoder), + size_in_queue_(0), + update_stream_size_(0), + handler_(handler) { + DCHECK(loop_); + DCHECK(decoder_.get()); + DCHECK(handler_); +} + +ClientConnection::~ClientConnection() { + // TODO(hclam): When we shut down the viewer we may have to close the + // jingle channel. +} + +void ClientConnection::SendInitClientMessage(int width, int height) { + DCHECK_EQ(loop_, MessageLoop::current()); + DCHECK(!update_stream_size_); + DCHECK(channel_.get()); + + chromotocol_pb::HostMessage msg; + msg.mutable_init_client()->set_width(width); + msg.mutable_init_client()->set_height(height); + DCHECK(msg.IsInitialized()); + channel_->Write(SerializeAndFrameMessage(msg)); +} + +void ClientConnection::SendBeginUpdateStreamMessage() { + DCHECK_EQ(loop_, MessageLoop::current()); + DCHECK(channel_.get()); + + chromotocol_pb::HostMessage msg; + msg.mutable_begin_update_stream(); + DCHECK(msg.IsInitialized()); + + scoped_refptr<DataBuffer> data = SerializeAndFrameMessage(msg); + DCHECK(!update_stream_size_); + update_stream_size_ += data->GetDataSize(); + channel_->Write(data); +} + +void ClientConnection::SendUpdateStreamPacketMessage( + chromotocol_pb::UpdateStreamPacketHeader* header, + scoped_refptr<DataBuffer> data) { + DCHECK_EQ(loop_, MessageLoop::current()); + DCHECK(channel_.get()); + + chromotocol_pb::HostMessage msg; + msg.mutable_update_stream_packet()->mutable_header()->CopyFrom(*header); + // TODO(hclam): This introduce one memory copy. Eliminate it. + msg.mutable_update_stream_packet()->set_data( + data->GetData(), data->GetDataSize()); + DCHECK(msg.IsInitialized()); + + scoped_refptr<DataBuffer> encoded_data = SerializeAndFrameMessage(msg); + update_stream_size_ += data->GetDataSize(); + channel_->Write(encoded_data); +} + +void ClientConnection::SendEndUpdateStreamMessage() { + DCHECK_EQ(loop_, MessageLoop::current()); + DCHECK(channel_.get()); + + chromotocol_pb::HostMessage msg; + msg.mutable_end_update_stream(); + DCHECK(msg.IsInitialized()); + + scoped_refptr<DataBuffer> data = SerializeAndFrameMessage(msg); + update_stream_size_ += data->GetDataSize(); + channel_->Write(data); + + // Here's some logic to help finding the average update stream size. + size_in_queue_ += update_stream_size_; + size_queue_.push_back(update_stream_size_); + if (size_queue_.size() > kAverageUpdateStream) { + size_in_queue_ -= size_queue_.front(); + size_queue_.pop_front(); + DCHECK_GE(size_in_queue_, 0); + } + update_stream_size_ = 0; +} + +int ClientConnection::GetPendingUpdateStreamMessages() { + DCHECK_EQ(loop_, MessageLoop::current()); + + if (!size_queue_.size()) + return 0; + int average_size = size_in_queue_ / size_queue_.size(); + if (!average_size) + return 0; + return channel_->write_buffer_size() / average_size; +} + +void ClientConnection::Disconnect() { + DCHECK_EQ(loop_, MessageLoop::current()); + + DCHECK(channel_.get()); + channel_->Close(); +} + +void ClientConnection::OnStateChange(JingleChannel* channel, + JingleChannel::State state) { + DCHECK(channel); + loop_->PostTask(FROM_HERE, + NewRunnableMethod(this, &ClientConnection::StateChangeTask, state)); +} + +void ClientConnection::OnPacketReceived(JingleChannel* channel, + scoped_refptr<DataBuffer> data) { + DCHECK_EQ(channel_.get(), channel); + loop_->PostTask(FROM_HERE, + NewRunnableMethod(this, &ClientConnection::PacketReceivedTask, data)); +} + +void ClientConnection::StateChangeTask(JingleChannel::State state) { + DCHECK_EQ(loop_, MessageLoop::current()); + + DCHECK(handler_); + switch(state) { + case JingleChannel::CONNECTING: + break; + // Don't care about this message. + case JingleChannel::OPEN: + handler_->OnConnectionOpened(this); + break; + case JingleChannel::CLOSED: + handler_->OnConnectionClosed(this); + break; + case JingleChannel::FAILED: + handler_->OnConnectionFailed(this); + break; + default: + // We shouldn't receive other states. + NOTREACHED(); + } +} + +void ClientConnection::PacketReceivedTask(scoped_refptr<DataBuffer> data) { + DCHECK_EQ(loop_, MessageLoop::current()); + + // Use the decoder to parse incoming data. + DCHECK(decoder_.get()); + ClientMessageList list; + decoder_->ParseClientMessages(data, &list); + + // Then submit the messages to the handler. + DCHECK(handler_); + handler_->HandleMessages(this, &list); +} + +} // namespace remoting diff --git a/remoting/host/client_connection.h b/remoting/host/client_connection.h new file mode 100644 index 0000000..13318c3 --- /dev/null +++ b/remoting/host/client_connection.h @@ -0,0 +1,141 @@ +// 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. + +#ifndef REMOTING_HOST_CLIENT_CONNECTION_H_ +#define REMOTING_HOST_CLIENT_CONNECTION_H_ + +#include <deque> +#include <vector> + +#include "base/message_loop.h" +#include "base/ref_counted.h" +#include "base/scoped_ptr.h" +#include "remoting/base/protocol_decoder.h" +#include "remoting/base/protocol/chromotocol.pb.h" +#include "remoting/jingle_glue/jingle_channel.h" + +namespace media { + +class DataBuffer; + +} // namespace media + +namespace remoting { + +// This class represents a remote viewer connected to the chromoting host +// through a libjingle connection. A viewer object is responsible for sending +// screen updates and other messages to the remote viewer. It is also +// responsible for receiving and parsing data from the remote viewer and +// delegating events to the event handler. +class ClientConnection : public base::RefCountedThreadSafe<ClientConnection>, + public JingleChannel::Callback { + public: + class EventHandler { + public: + virtual ~EventHandler() {} + + // Handles an event received by the ClientConnection. Receiver will own the + // ClientMessages in ClientMessageList and needs to delete them. + // Note that the sender of messages will not reference messages + // again so it is okay to clear |messages| in this method. + virtual void HandleMessages(ClientConnection* viewer, + ClientMessageList* messages) = 0; + + // Called when the network connection is opened. + virtual void OnConnectionOpened(ClientConnection* viewer) = 0; + + // Called when the network connection is closed. + virtual void OnConnectionClosed(ClientConnection* viewer) = 0; + + // Called when the network connection has failed. + virtual void OnConnectionFailed(ClientConnection* viewer) = 0; + }; + + // Constructs a ClientConnection object. |message_loop| is the message loop + // that this object runs on. A viewer object receives events and messages from + // a libjingle channel, these events are delegated to |handler|. + // It is guranteed that |handler| is called only on the |message_loop|. + ClientConnection(MessageLoop* message_loop, + ProtocolDecoder* decoder, + EventHandler* handler); + + virtual ~ClientConnection(); + + virtual void set_jingle_channel(JingleChannel* channel) { + channel_ = channel; + } + + // Returns the channel in use. + virtual JingleChannel* jingle_channel() { return channel_; } + + // Send information to the client for initialization. + virtual void SendInitClientMessage(int width, int height); + + // Notifies the viewer the start of an update stream. + virtual void SendBeginUpdateStreamMessage(); + + // Send encoded update stream data to the viewer. The viewer + // should not take ownership of the data. + virtual void SendUpdateStreamPacketMessage( + chromotocol_pb::UpdateStreamPacketHeader* header, + scoped_refptr<media::DataBuffer> data); + + // Notifies the viewer the update stream has ended. + virtual void SendEndUpdateStreamMessage(); + + // Gets the number of update stream messages not yet transmitted. + // Note that the value returned is an estimate using average size of the + // most recent update streams. + // TODO(hclam): Report this number accurately. + virtual int GetPendingUpdateStreamMessages(); + + // Disconnect the remote viewer. + virtual void Disconnect(); + + ///////////////////////////////////////////////////////////////////////////// + // JingleChannel::Callback implmentations + virtual void OnStateChange(JingleChannel* channel, + JingleChannel::State state); + virtual void OnPacketReceived(JingleChannel* channel, + scoped_refptr<media::DataBuffer> data); + + protected: + // Protected constructor used by unit test. + ClientConnection() {} + + private: + // Process a libjingle state change event on the |loop_|. + void StateChangeTask(JingleChannel::State state); + + // Process a data buffer received from libjingle. + void PacketReceivedTask(scoped_refptr<media::DataBuffer> data); + + // The libjingle channel used to send and receive data from the remote viewer. + scoped_refptr<JingleChannel> channel_; + + // The message loop that this object runs on. + MessageLoop* loop_; + + // An object used by the ClientConnection to decode data received from the + // network. + scoped_ptr<ProtocolDecoder> decoder_; + + // A queue to count the sizes of the last 10 update streams. + std::deque<int> size_queue_; + + // Count the sum of sizes in the queue. + int size_in_queue_; + + // Measure the number of bytes of the current upstream stream. + int update_stream_size_; + + // Event handler for handling events sent from this object. + EventHandler* handler_; + + DISALLOW_COPY_AND_ASSIGN(ClientConnection); +}; + +} // namespace remoting + +#endif // REMOTING_HOST_CLIENT_CONNECTION_H_ diff --git a/remoting/host/client_connection_unittest.cc b/remoting/host/client_connection_unittest.cc new file mode 100644 index 0000000..1256f25 --- /dev/null +++ b/remoting/host/client_connection_unittest.cc @@ -0,0 +1,99 @@ +// 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 "base/message_loop.h" +#include "media/base/data_buffer.h" +#include "remoting/base/mock_objects.h" +#include "remoting/host/client_connection.h" +#include "remoting/host/mock_objects.h" +#include "remoting/jingle_glue/mock_objects.h" +#include "testing/gmock/include/gmock/gmock.h" + +using ::testing::_; +using ::testing::NotNull; + +namespace remoting { + +class ClientConnectionTest : public testing::Test { + public: + ClientConnectionTest() { + } + + protected: + virtual void SetUp() { + decoder_ = new MockProtocolDecoder(); + channel_ = new MockJingleChannel(); + + // Allocate a ClientConnection object with the mock objects. we give the + // ownership of decoder to the viewer. + viewer_ = new ClientConnection(&message_loop_, + decoder_, + &handler_); + + viewer_->set_jingle_channel(channel_.get()); + } + + MessageLoop message_loop_; + MockProtocolDecoder* decoder_; + MockClientConnectionEventHandler handler_; + scoped_refptr<MockJingleChannel> channel_; + scoped_refptr<ClientConnection> viewer_; + + private: + DISALLOW_COPY_AND_ASSIGN(ClientConnectionTest); +}; + +TEST_F(ClientConnectionTest, SendUpdateStream) { + // Tell the viewer we are starting an update stream. + EXPECT_CALL(*channel_, Write(_)); + viewer_->SendBeginUpdateStreamMessage(); + + // Then send the actual data. + EXPECT_CALL(*channel_, Write(_)); + chromotocol_pb::UpdateStreamPacketHeader* header + = new chromotocol_pb::UpdateStreamPacketHeader(); + header->set_x(0); + header->set_y(0); + header->set_width(640); + header->set_height(480); + scoped_refptr<media::DataBuffer> data = new media::DataBuffer(10); + viewer_->SendUpdateStreamPacketMessage(header, data); + delete header; + + // Send the end of update message. + EXPECT_CALL(*channel_, Write(_)); + viewer_->SendEndUpdateStreamMessage(); + + // And then close the connection to ClientConnection. + EXPECT_CALL(*channel_, Close()); + viewer_->Disconnect(); +} + +TEST_F(ClientConnectionTest, StateChange) { + EXPECT_CALL(handler_, OnConnectionOpened(viewer_.get())); + viewer_->OnStateChange(channel_.get(), JingleChannel::OPEN); + message_loop_.RunAllPending(); + + EXPECT_CALL(handler_, OnConnectionClosed(viewer_.get())); + viewer_->OnStateChange(channel_.get(), JingleChannel::CLOSED); + message_loop_.RunAllPending(); + + EXPECT_CALL(handler_, OnConnectionFailed(viewer_.get())); + viewer_->OnStateChange(channel_.get(), JingleChannel::FAILED); + message_loop_.RunAllPending(); +} + +TEST_F(ClientConnectionTest, ParseMessages) { + scoped_refptr<media::DataBuffer> data; + + // Give the data to the ClientConnection, it will use ProtocolDecoder to + // decode the messages. + EXPECT_CALL(*decoder_, ParseClientMessages(data, NotNull())); + EXPECT_CALL(handler_, HandleMessages(viewer_.get(), NotNull())); + + viewer_->OnPacketReceived(channel_.get(), data); + message_loop_.RunAllPending(); +} + +} // namespace remoting diff --git a/remoting/host/differ_block.cc b/remoting/host/differ_block.cc new file mode 100644 index 0000000..c42c171 --- /dev/null +++ b/remoting/host/differ_block.cc @@ -0,0 +1,28 @@ +// 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 "differ_block.h" + +#include <stdlib.h> + +namespace remoting { + +// TODO(fbarchard): Use common header for block size. +int kBlockWidth = 32; +int kBlockHeight = 32; +int kBytesPerPixel = 3; + +int BlockDifference(const uint8* image1, const uint8* image2, int stride) { + int diff = 0; + for (int y = 0; y < kBlockHeight; ++y) { + for (int x = 0; x < kBlockWidth * kBytesPerPixel; ++x) { + diff += abs(image1[x] - image2[x]); + } + image1 += stride; + image2 += stride; + } + return diff; +} + +} // namespace remoting diff --git a/remoting/host/differ_block.h b/remoting/host/differ_block.h new file mode 100644 index 0000000..7b319b9 --- /dev/null +++ b/remoting/host/differ_block.h @@ -0,0 +1,23 @@ +// 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. + +#ifndef REMOTING_HOST_DIFFER_BLOCK_H_ +#define REMOTING_HOST_DIFFER_BLOCK_H_ + +#include "base/basictypes.h" + +namespace remoting { + +// Low level functions to return the difference between 2 blocks of pixels. +// The amount of difference is returned as an int. +// zero means the blocks are identical. +// larger values indicate larger changes. +// Pixel format of the captured screen may be platform specific, but constant. +// Size of block is constant. + +int BlockDifference(const uint8* image1, const uint8* image2, int stride); + +} // namespace remoting + +#endif // REMOTING_HOST_DIFFER_BLOCK_H_ diff --git a/remoting/host/differ_block_unittest.cc b/remoting/host/differ_block_unittest.cc new file mode 100644 index 0000000..02bd0b3 --- /dev/null +++ b/remoting/host/differ_block_unittest.cc @@ -0,0 +1,46 @@ +// 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 "media/base/data_buffer.h" +#include "remoting/host/differ_block.h" +#include "testing/gmock/include/gmock/gmock.h" + +namespace remoting { + +static const int kWidth = 32; +static const int kHeight = 32; +static const int kBytesPerPixel = 3; + +static void GenerateData(uint8* data, int size) { + for (int i = 0; i < size; ++i) { + data[i] = i; + } +} + +class EncodeDoneHandler + : public base::RefCountedThreadSafe<EncodeDoneHandler> { + public: + MOCK_METHOD0(EncodeDone, void()); +}; + +TEST(BlockDifferenceTest, BlockDifference) { + // Prepare 2 blocks to compare. + uint8 block1[kHeight * kWidth * kBytesPerPixel]; + uint8 block2[kHeight * kWidth * kBytesPerPixel]; + GenerateData(block1, sizeof(block1)); + memcpy(block2, block1, sizeof(block2)); + + // These blocks should match. + int same = BlockDifference(block1, block2, kWidth * kBytesPerPixel); + EXPECT_EQ(0, same); + + // Change block2 a little. + block2[7] += 3; + block2[sizeof(block2)-1] -= 5; + // These blocks should not match. The difference should be 8. + int not_same = BlockDifference(block1, block2, kWidth * kBytesPerPixel); + EXPECT_EQ(8, not_same); +} + +} // namespace remoting diff --git a/remoting/host/encoder.h b/remoting/host/encoder.h new file mode 100644 index 0000000..e3af66b --- /dev/null +++ b/remoting/host/encoder.h @@ -0,0 +1,63 @@ +// 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. + +#ifndef REMOTING_HOST_ENCODER_H_ +#define REMOTING_HOST_ENCODER_H_ + +#include "base/basictypes.h" +#include "base/task.h" +#include "remoting/base/protocol/chromotocol.pb.h" +#include "remoting/host/capturer.h" + +namespace media { + +class DataBuffer; + +} // namespace media + +namespace remoting { + +// A class to perform the task of encoding a continous stream of +// images. +// This class operates asynchronously to enable maximum throughput. +class Encoder { + public: + virtual ~Encoder() {} + + // Encode an image stored in |input_data|. |dirty_rects| contains + // regions of update since last encode. + // + // If |key_frame| is true, the encoder should not reference + // previous encode and encode the full frame. + // + // When encoded data is available, partial or full |data_available_task| + // is called, data can be read from |data| and size is |data_size|. + // After the last data available event and encode has completed, + // |encode_done| is set to true and |data_available_task| is deleted. + // + // Note that |input_data| and |stride| are arrays of 3 elements. + // + // Implementation has to ensure that when |data_available_task| is called + // output parameters are stable. + virtual void Encode(const DirtyRects& dirty_rects, + const uint8** input_data, + const int* strides, + bool key_frame, + chromotocol_pb::UpdateStreamPacketHeader* header, + scoped_refptr<media::DataBuffer>* output_data, + bool* encode_done, + Task* data_available_task) = 0; + + // Set the dimension of the incoming images. Need to call this before + // calling Encode(). + virtual void SetSize(int width, int height) = 0; + + // Set the pixel format of the incoming images. Need to call this before + // calling Encode(). + virtual void SetPixelFormat(chromotocol_pb::PixelFormat pixel_format) = 0; +}; + +} // namespace remoting + +#endif // REMOTING_HOST_ENCODER_H_ diff --git a/remoting/host/encoder_verbatim.cc b/remoting/host/encoder_verbatim.cc new file mode 100644 index 0000000..0ef7677 --- /dev/null +++ b/remoting/host/encoder_verbatim.cc @@ -0,0 +1,97 @@ +// 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/encoder_verbatim.h" + +#include "gfx/rect.h" +#include "media/base/data_buffer.h" +#include "remoting/base/protocol/chromotocol.pb.h" + +namespace remoting { + +using chromotocol_pb::UpdateStreamPacketHeader; +using media::DataBuffer; + +void EncoderVerbatim::Encode(const DirtyRects& dirty_rects, + const uint8** input_data, + const int* strides, + bool key_frame, + UpdateStreamPacketHeader* header, + scoped_refptr<DataBuffer>* output_data, + bool* encode_done, + Task* data_available_task) { + int num_rects = dirty_rects.size(); + for (int i = 0; i < num_rects; i++) { + if (EncodeRect(dirty_rects[i], input_data, strides, header, output_data)) { + *encode_done = (i == num_rects - 1); // Set for last rect. + data_available_task->Run(); + } + } + + delete data_available_task; +} + +void EncoderVerbatim::SetSize(int width, int height) { + width_ = width; + height_ = height; +} + +void EncoderVerbatim::SetPixelFormat(chromotocol_pb::PixelFormat pixel_format) { + // These are sorted so that the most common formats are checked first. + if (pixel_format == chromotocol_pb::PixelFormatRgb24) { + bytes_per_pixel_ = 3; + } else if (pixel_format == chromotocol_pb::PixelFormatRgb565) { + bytes_per_pixel_ = 2; + } else if (pixel_format == chromotocol_pb::PixelFormatRgb32) { + bytes_per_pixel_ = 4; + } else if (pixel_format != chromotocol_pb::PixelFormatAscii) { + bytes_per_pixel_ = 1; + } else { + NOTREACHED() << "Pixel format not supported"; + } +} + +bool EncoderVerbatim::EncodeRect(const gfx::Rect& dirty, + const uint8** input_data, + const int* strides, + UpdateStreamPacketHeader* header, + scoped_refptr<DataBuffer>* output_data) { + const int kPlanes = 3; + + // Calculate the size of output. + int output_size = 0; + for (int i = 0; i < kPlanes; ++i) { + // TODO(hclam): Handle YUV since the height would be different. + output_size += strides[i] * height_; + } + + header->set_x(dirty.x()); + header->set_y(dirty.y()); + header->set_width(dirty.width()); + header->set_height(dirty.height()); + header->set_encoding(chromotocol_pb::EncodingNone); + + *output_data = new DataBuffer(output_size); + (*output_data)->SetDataSize(output_size); + + uint8* out = (*output_data)->GetWritableData(); + for (int i = 0; i < kPlanes; ++i) { + const uint8* in = input_data[i]; + // Skip over planes that don't have data. + if (!in) + continue; + + // TODO(hclam): Handle YUV since the height would be different. + for (int j = 0; j < height_; ++j) { + int row_size = width_ * bytes_per_pixel_; + DCHECK_LE(row_size, strides[i]); + memcpy(out, in, row_size); + in += strides[i]; + out += row_size; + } + } + return true; +} + +} // namespace remoting diff --git a/remoting/host/encoder_verbatim.h b/remoting/host/encoder_verbatim.h new file mode 100644 index 0000000..2b5e39d --- /dev/null +++ b/remoting/host/encoder_verbatim.h @@ -0,0 +1,47 @@ +// 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. + +#ifndef REMOTING_HOST_ENCODER_VERBATIM_H_ +#define REMOTING_HOST_ENCODER_VERBATIM_H_ + +#include "remoting/host/encoder.h" + +namespace remoting { + +// EncoderVerbatim implements Encoder and simply copies input to the output +// buffer verbatim. +class EncoderVerbatim : public Encoder { + public: + EncoderVerbatim() + : width_(0), height_(0), bytes_per_pixel_(0) {} + virtual ~EncoderVerbatim() {} + + virtual void Encode(const DirtyRects& dirty_rects, + const uint8** input_data, + const int* strides, + bool key_frame, + chromotocol_pb::UpdateStreamPacketHeader* header, + scoped_refptr<media::DataBuffer>* output_data, + bool* encode_done, + Task* data_available_task); + virtual void SetSize(int width, int height); + virtual void SetPixelFormat(chromotocol_pb::PixelFormat pixel_format); + + private: + // Encode a single dirty rect. Called by Encode(). + // Returns false if there is an error. + bool EncodeRect(const gfx::Rect& dirty, + const uint8** input_data, + const int* strides, + chromotocol_pb::UpdateStreamPacketHeader* header, + scoped_refptr<media::DataBuffer>* output_data); + + int width_; + int height_; + int bytes_per_pixel_; +}; + +} // namespace remoting + +#endif // REMOTING_HOST_ENCODER_VERBATIM_H_ diff --git a/remoting/host/encoder_vp8.cc b/remoting/host/encoder_vp8.cc new file mode 100644 index 0000000..a1e45e7 --- /dev/null +++ b/remoting/host/encoder_vp8.cc @@ -0,0 +1,133 @@ +// 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 "base/logging.h" +#include "media/base/callback.h" +#include "media/base/data_buffer.h" +#include "remoting/host/encoder_vp8.h" + +extern "C" { +// TODO(garykac): Rix with correct path to vp8 header. +#include "remoting/third_party/on2/include/vp8cx.h" +} + +namespace remoting { + +EncoderVp8::EncoderVp8() + : initialized_(false), + last_timestamp_(0) { +} + +EncoderVp8::~EncoderVp8() { +} + +bool EncoderVp8::Init() { + // TODO(hclam): Now always assume we receive YV12. May need to extend this + // so we can do color space conversion manually. + image_.fmt = IMG_FMT_YV12; + image_.w = width_; + image_.h = height_; + + on2_codec_enc_cfg_t config; + on2_codec_err_t result = on2_codec_enc_config_default(&on2_codec_vp8_cx_algo, + &config, 0); + + // TODO(hclam): Adjust the parameters. + config.g_w = width_; + config.g_h = height_; + config.g_pass = ON2_RC_ONE_PASS; + config.g_profile = 1; + config.g_threads = 2; + config.rc_target_bitrate = 1000000; + config.rc_min_quantizer = 0; + config.rc_max_quantizer = 15; + config.g_timebase.num = 1; + config.g_timebase.den = 30; + + if (on2_codec_enc_init(&codec_, &on2_codec_vp8_cx_algo, &config, 0)) + return false; + + on2_codec_control_(&codec_, VP8E_SET_CPUUSED, -15); + return true; +} + +void EncoderVp8::Encode(const DirtyRects& dirty_rects, + const uint8** input_data, + const int* strides, + bool key_frame, + chromotocol_pb::UpdateStreamPacketHeader* header, + scoped_refptr<media::DataBuffer>* output_data, + bool* encode_done, + Task* data_available_task) { + // This will allow the task be called when this method exits. + media::AutoTaskRunner task(data_available_task); + *encode_done = false; + + // TODO(hclam): We only initialize the encoder once. We may have to + // allow encoder be initialized with difference sizes. + if (!initialized_) { + if (!Init()) { + LOG(ERROR) << "Can't initialize VP8 encoder"; + return; + } + initialized_ = true; + } + + // Assume the capturer has done the color space conversion. + if (!input_data || !strides) + return; + + image_.planes[0] = (unsigned char*)input_data[0]; + image_.planes[1] = (unsigned char*)input_data[1]; + image_.planes[2] = (unsigned char*)input_data[2]; + image_.stride[0] = strides[0]; + image_.stride[1] = strides[1]; + image_.stride[2] = strides[2]; + + // Do the actual encoding. + if (on2_codec_encode(&codec_, &image_, + last_timestamp_, 1, 0, ON2_DL_REALTIME)) { + return; + } + + // TODO(hclam): fix this. + last_timestamp_ += 100; + + // Read the encoded data. + on2_codec_iter_t iter = NULL; + bool got_data = false; + + // TODO(hclam: We assume one frame of input will get exactly one frame of + // output. This assumption may not be valid. + while (!got_data) { + on2_codec_cx_pkt_t* packet = on2_codec_get_cx_data(&codec_, &iter); + if (!packet) + continue; + + switch (packet->kind) { + case ON2_CODEC_CX_FRAME_PKT: + got_data = true; + *encode_done = true; + *output_data = new media::DataBuffer(packet->data.frame.sz); + memcpy((*output_data)->GetWritableData(), + packet->data.frame.buf, + packet->data.frame.sz); + break; + default: + break; + } + } + return; +} + +void EncoderVp8::SetSize(int width, int height) { + width_ = width; + height_ = height; +} + +void EncoderVp8::SetPixelFormat(PixelFormat pixel_format) { + pixel_format_ = pixel_format; +} + +} // namespace remoting diff --git a/remoting/host/encoder_vp8.h b/remoting/host/encoder_vp8.h new file mode 100644 index 0000000..e8c73b7 --- /dev/null +++ b/remoting/host/encoder_vp8.h @@ -0,0 +1,61 @@ +// 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. + +#ifndef REMOTING_HOST_ENCODER_VP8_H_ +#define REMOTING_HOST_ENCODER_VP8_H_ + +#include "remoting/host/encoder.h" + +#include "remoting/base/protocol/chromotocol.pb.h" + +extern "C" { +// TODO(garykac): fix this link with the correct path to on2 +#include "remoting/third_party/on2/include/on2_encoder.h" +} // extern "C" + +namespace media { + +class DataBuffer; + +} // namespace media + +namespace remoting { + +// A class that uses VP8 to perform encoding. +class EncoderVp8 : public Encoder { + public: + EncoderVp8(); + virtual ~EncoderVp8(); + + virtual void Encode(const DirtyRects& dirty_rects, + const uint8** input_data, + const int* strides, + bool key_frame, + chromotocol_pb::UpdateStreamPacketHeader* header, + scoped_refptr<media::DataBuffer>* output_data, + bool* encode_done, + Task* data_available_task); + virtual void SetSize(int width, int height); + virtual void SetPixelFormat(PixelFormat pixel_format); + + private: + // Setup the VP8 encoder. + bool Init(); + + // True if the encoder is initialized. + bool initialized_; + + int width_; + int height_; + PixelFormat pixel_format_; + on2_codec_ctx_t codec_; + on2_image_t image_; + int last_timestamp_; + + DISALLOW_COPY_AND_ASSIGN(EncoderVp8); +}; + +} // namespace remoting + +#endif // REMOTING_HOST_ENCODER_VP8_H_ diff --git a/remoting/host/encoder_vp8_unittest.cc b/remoting/host/encoder_vp8_unittest.cc new file mode 100644 index 0000000..2dbc81b --- /dev/null +++ b/remoting/host/encoder_vp8_unittest.cc @@ -0,0 +1,69 @@ +// 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 "media/base/data_buffer.h" +#include "remoting/base/pixel_format.h" +#include "remoting/host/encoder_vp8.h" +#include "testing/gmock/include/gmock/gmock.h" + +namespace remoting { + +static const int kWidth = 1024; +static const int kHeight = 768; +static const PixelFormat kPixelFormat = kPixelFormat_YV12; + +static void GenerateData(uint8* data, int size) { + for (int i = 0; i < size; ++i) { + data[i] = i; + } +} + +class EncodeDoneHandler + : public base::RefCountedThreadSafe<EncodeDoneHandler> { + public: + MOCK_METHOD0(EncodeDone, void()); +}; + +TEST(EncoderVp8Test, SimpleEncode) { + EncoderVp8 encoder; + encoder.SetSize(kWidth, kHeight); + encoder.SetPixelFormat(kPixelFormat); + + DirtyRects rects; + rects.push_back(gfx::Rect(kWidth, kHeight)); + + // Prepare memory for encoding. + int strides[3]; + strides[0] = kWidth; + strides[1] = strides[2] = kWidth / 2; + + uint8* planes[3]; + planes[0] = new uint8[kWidth * kHeight]; + planes[1] = new uint8[kWidth * kHeight / 4]; + planes[2] = new uint8[kWidth * kHeight / 4]; + GenerateData(planes[0], kWidth * kHeight); + GenerateData(planes[1], kWidth * kHeight / 4); + GenerateData(planes[2], kWidth * kHeight / 4); + + scoped_refptr<EncodeDoneHandler> handler = new EncodeDoneHandler(); + chromotocol_pb::UpdateStreamPacketHeader* header + = new chromotocol_pb::UpdateStreamPacketHeader(); + scoped_refptr<media::DataBuffer> encoded_data; + bool encode_done = false; + EXPECT_CALL(*handler, EncodeDone()); + encoder.Encode(rects, const_cast<const uint8**>(planes), + strides, true, header, &encoded_data, &encode_done, + NewRunnableMethod(handler.get(), + &EncodeDoneHandler::EncodeDone)); + + EXPECT_TRUE(encode_done); + ASSERT_TRUE(encoded_data.get()); + EXPECT_NE(0u, encoded_data->GetBufferSize()); + + delete [] planes[0]; + delete [] planes[1]; + delete [] planes[2]; +} + +} // namespace remoting diff --git a/remoting/host/event_executor.h b/remoting/host/event_executor.h new file mode 100644 index 0000000..0623464 --- /dev/null +++ b/remoting/host/event_executor.h @@ -0,0 +1,34 @@ +// 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. + +#ifndef REMOTING_HOST_EVENT_EXECUTOR_H_ +#define REMOTING_HOST_EVENT_EXECUTOR_H_ + +#include <vector> + +#include "remoting/base/protocol_decoder.h" + +namespace remoting { + +// An interface that defines the behavior of an event executor object. +// An event executor is to perform actions on the host machine. For example +// moving the mouse cursor, generating keyboard events and manipulating +// clipboards. +class EventExecutor { + public: + EventExecutor() {} + virtual ~EventExecutor() {} + + // Handles input events from ClientMessageList and removes them from the + // list. + virtual void HandleInputEvents(ClientMessageList* messages) = 0; + // TODO(hclam): Define actions for clipboards. + + private: + DISALLOW_COPY_AND_ASSIGN(EventExecutor); +}; + +} // namespace remoting + +#endif // REMOTING_HOST_EVENT_EXECUTOR_H_ diff --git a/remoting/host/event_executor_linux.cc b/remoting/host/event_executor_linux.cc new file mode 100644 index 0000000..2ce4fd4 --- /dev/null +++ b/remoting/host/event_executor_linux.cc @@ -0,0 +1,18 @@ +// 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/event_executor_linux.h" + +namespace remoting { + +EventExecutorLinux::EventExecutorLinux() { +} + +EventExecutorLinux::~EventExecutorLinux() { +} + +void EventExecutorLinux::HandleInputEvents(ClientMessageList* messages) { +} + +} // namespace remoting diff --git a/remoting/host/event_executor_linux.h b/remoting/host/event_executor_linux.h new file mode 100644 index 0000000..f05a6dc --- /dev/null +++ b/remoting/host/event_executor_linux.h @@ -0,0 +1,28 @@ +// 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. + +#ifndef REMOTING_HOST_EVENT_EXECUTOR_LINUX_H_ +#define REMOTING_HOST_EVENT_EXECUTOR_LINUX_H_ + +#include <vector> + +#include "remoting/host/event_executor.h" + +namespace remoting { + +// A class to generate events on Linux. +class EventExecutorLinux : public EventExecutor { + public: + EventExecutorLinux(); + virtual ~EventExecutorLinux(); + + virtual void HandleInputEvents(ClientMessageList* messages); + + private: + DISALLOW_COPY_AND_ASSIGN(EventExecutorLinux); +}; + +} // namespace remoting + +#endif // REMOTING_HOST_EVENT_EXECUTOR_LINUX_H_ diff --git a/remoting/host/event_executor_mac.cc b/remoting/host/event_executor_mac.cc new file mode 100644 index 0000000..5d6737b --- /dev/null +++ b/remoting/host/event_executor_mac.cc @@ -0,0 +1,18 @@ +// 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/event_executor_mac.h" + +namespace remoting { + +EventExecutorMac::EventExecutorMac() { +} + +EventExecutorMac::~EventExecutorMac() { +} + +void EventExecutorMac::HandleInputEvents(ClientMessageList* messages) { +} + +} // namespace remoting diff --git a/remoting/host/event_executor_mac.h b/remoting/host/event_executor_mac.h new file mode 100644 index 0000000..587661d --- /dev/null +++ b/remoting/host/event_executor_mac.h @@ -0,0 +1,28 @@ +// 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. + +#ifndef REMOTING_HOST_EVENT_EXECUTOR_MAC_H_ +#define REMOTING_HOST_EVENT_EXECUTOR_MAC_H_ + +#include <vector> + +#include "remoting/host/event_executor.h" + +namespace remoting { + +// A class to generate events on Mac. +class EventExecutorMac : public EventExecutor { + public: + EventExecutorMac(); + virtual ~EventExecutorMac(); + + virtual void HandleInputEvents(ClientMessageList* messages); + + private: + DISALLOW_COPY_AND_ASSIGN(EventExecutorMac); +}; + +} // namespace remoting + +#endif // REMOTING_HOST_EVENT_EXECUTOR_MAC_H_ diff --git a/remoting/host/event_executor_win.cc b/remoting/host/event_executor_win.cc new file mode 100644 index 0000000..cc7f8f8 --- /dev/null +++ b/remoting/host/event_executor_win.cc @@ -0,0 +1,405 @@ +// 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/event_executor_win.h" + +#include <windows.h> +#include "base/keyboard_codes.h" +#include "base/stl_util-inl.h" + +namespace remoting { + +// TODO(hclam): Move this method to base. +// TODO(hclam): Using values look ugly, change it to something else. +static base::KeyboardCode WindowsKeyCodeForPosixKeyCode(int keycode) { + switch (keycode) { + case 0x08: + return base::VKEY_BACK; + case 0x09: + return base::VKEY_TAB; + case 0x0C: + return base::VKEY_CLEAR; + case 0x0D: + return base::VKEY_RETURN; + case 0x10: + return base::VKEY_SHIFT; + case 0x11: + return base::VKEY_CONTROL; + case 0x12: + return base::VKEY_MENU; + case 0x13: + return base::VKEY_PAUSE; + case 0x14: + return base::VKEY_CAPITAL; + case 0x15: + return base::VKEY_KANA; + case 0x17: + return base::VKEY_JUNJA; + case 0x18: + return base::VKEY_FINAL; + case 0x19: + return base::VKEY_KANJI; + case 0x1B: + return base::VKEY_ESCAPE; + case 0x1C: + return base::VKEY_CONVERT; + case 0x1D: + return base::VKEY_NONCONVERT; + case 0x1E: + return base::VKEY_ACCEPT; + case 0x1F: + return base::VKEY_MODECHANGE; + case 0x20: + return base::VKEY_SPACE; + case 0x21: + return base::VKEY_PRIOR; + case 0x22: + return base::VKEY_NEXT; + case 0x23: + return base::VKEY_END; + case 0x24: + return base::VKEY_HOME; + case 0x25: + return base::VKEY_LEFT; + case 0x26: + return base::VKEY_UP; + case 0x27: + return base::VKEY_RIGHT; + case 0x28: + return base::VKEY_DOWN; + case 0x29: + return base::VKEY_SELECT; + case 0x2A: + return base::VKEY_PRINT; + case 0x2B: + return base::VKEY_EXECUTE; + case 0x2C: + return base::VKEY_SNAPSHOT; + case 0x2D: + return base::VKEY_INSERT; + case 0x2E: + return base::VKEY_DELETE; + case 0x2F: + return base::VKEY_HELP; + case 0x30: + return base::VKEY_0; + case 0x31: + return base::VKEY_1; + case 0x32: + return base::VKEY_2; + case 0x33: + return base::VKEY_3; + case 0x34: + return base::VKEY_4; + case 0x35: + return base::VKEY_5; + case 0x36: + return base::VKEY_6; + case 0x37: + return base::VKEY_7; + case 0x38: + return base::VKEY_8; + case 0x39: + return base::VKEY_9; + case 0x41: + return base::VKEY_A; + case 0x42: + return base::VKEY_B; + case 0x43: + return base::VKEY_C; + case 0x44: + return base::VKEY_D; + case 0x45: + return base::VKEY_E; + case 0x46: + return base::VKEY_F; + case 0x47: + return base::VKEY_G; + case 0x48: + return base::VKEY_H; + case 0x49: + return base::VKEY_I; + case 0x4A: + return base::VKEY_J; + case 0x4B: + return base::VKEY_K; + case 0x4C: + return base::VKEY_L; + case 0x4D: + return base::VKEY_M; + case 0x4E: + return base::VKEY_N; + case 0x4F: + return base::VKEY_O; + case 0x50: + return base::VKEY_P; + case 0x51: + return base::VKEY_Q; + case 0x52: + return base::VKEY_R; + case 0x53: + return base::VKEY_S; + case 0x54: + return base::VKEY_T; + case 0x55: + return base::VKEY_U; + case 0x56: + return base::VKEY_V; + case 0x57: + return base::VKEY_W; + case 0x58: + return base::VKEY_X; + case 0x59: + return base::VKEY_Y; + case 0x5A: + return base::VKEY_Z; + case 0x5B: + return base::VKEY_LWIN; + case 0x5C: + return base::VKEY_RWIN; + case 0x5D: + return base::VKEY_APPS; + case 0x5F: + return base::VKEY_SLEEP; + case 0x60: + return base::VKEY_NUMPAD0; + case 0x61: + return base::VKEY_NUMPAD1; + case 0x62: + return base::VKEY_NUMPAD2; + case 0x63: + return base::VKEY_NUMPAD3; + case 0x64: + return base::VKEY_NUMPAD4; + case 0x65: + return base::VKEY_NUMPAD5; + case 0x66: + return base::VKEY_NUMPAD6; + case 0x67: + return base::VKEY_NUMPAD7; + case 0x68: + return base::VKEY_NUMPAD8; + case 0x69: + return base::VKEY_NUMPAD9; + case 0x6A: + return base::VKEY_MULTIPLY; + case 0x6B: + return base::VKEY_ADD; + case 0x6C: + return base::VKEY_SEPARATOR; + case 0x6D: + return base::VKEY_SUBTRACT; + case 0x6E: + return base::VKEY_DECIMAL; + case 0x6F: + return base::VKEY_DIVIDE; + case 0x70: + return base::VKEY_F1; + case 0x71: + return base::VKEY_F2; + case 0x72: + return base::VKEY_F3; + case 0x73: + return base::VKEY_F4; + case 0x74: + return base::VKEY_F5; + case 0x75: + return base::VKEY_F6; + case 0x76: + return base::VKEY_F7; + case 0x77: + return base::VKEY_F8; + case 0x78: + return base::VKEY_F9; + case 0x79: + return base::VKEY_F10; + case 0x7A: + return base::VKEY_F11; + case 0x7B: + return base::VKEY_F12; + case 0x7C: + return base::VKEY_F13; + case 0x7D: + return base::VKEY_F14; + case 0x7E: + return base::VKEY_F15; + case 0x7F: + return base::VKEY_F16; + case 0x80: + return base::VKEY_F17; + case 0x81: + return base::VKEY_F18; + case 0x82: + return base::VKEY_F19; + case 0x83: + return base::VKEY_F20; + case 0x84: + return base::VKEY_F21; + case 0x85: + return base::VKEY_F22; + case 0x86: + return base::VKEY_F23; + case 0x87: + return base::VKEY_F24; + case 0x90: + return base::VKEY_NUMLOCK; + case 0x91: + return base::VKEY_SCROLL; + case 0xA0: + return base::VKEY_LSHIFT; + case 0xA1: + return base::VKEY_RSHIFT; + case 0xA2: + return base::VKEY_LCONTROL; + case 0xA3: + return base::VKEY_RCONTROL; + case 0xA4: + return base::VKEY_LMENU; + case 0xA5: + return base::VKEY_RMENU; + case 0xA6: + return base::VKEY_BROWSER_BACK; + case 0xA7: + return base::VKEY_BROWSER_FORWARD; + case 0xA8: + return base::VKEY_BROWSER_REFRESH; + case 0xA9: + return base::VKEY_BROWSER_STOP; + case 0xAA: + return base::VKEY_BROWSER_SEARCH; + case 0xAB: + return base::VKEY_BROWSER_FAVORITES; + case 0xAC: + return base::VKEY_BROWSER_HOME; + case 0xAD: + return base::VKEY_VOLUME_MUTE; + case 0xAE: + return base::VKEY_VOLUME_DOWN; + case 0xAF: + return base::VKEY_VOLUME_UP; + case 0xB0: + return base::VKEY_MEDIA_NEXT_TRACK; + case 0xB1: + return base::VKEY_MEDIA_PREV_TRACK; + case 0xB2: + return base::VKEY_MEDIA_STOP; + case 0xB3: + return base::VKEY_MEDIA_PLAY_PAUSE; + case 0xB4: + return base::VKEY_MEDIA_LAUNCH_MAIL; + case 0xB5: + return base::VKEY_MEDIA_LAUNCH_MEDIA_SELECT; + case 0xB6: + return base::VKEY_MEDIA_LAUNCH_APP1; + case 0xB7: + return base::VKEY_MEDIA_LAUNCH_APP2; + case 0xBA: + return base::VKEY_OEM_1; + case 0xBB: + return base::VKEY_OEM_PLUS; + case 0xBC: + return base::VKEY_OEM_COMMA; + case 0xBD: + return base::VKEY_OEM_MINUS; + case 0xBE: + return base::VKEY_OEM_PERIOD; + case 0xBF: + return base::VKEY_OEM_2; + case 0xC0: + return base::VKEY_OEM_3; + case 0xDB: + return base::VKEY_OEM_4; + case 0xDC: + return base::VKEY_OEM_5; + case 0xDD: + return base::VKEY_OEM_6; + case 0xDE: + return base::VKEY_OEM_7; + case 0xDF: + return base::VKEY_OEM_8; + case 0xE2: + return base::VKEY_OEM_102; + case 0xE5: + return base::VKEY_PROCESSKEY; + case 0xE7: + return base::VKEY_PACKET; + case 0xF6: + return base::VKEY_ATTN; + case 0xF7: + return base::VKEY_CRSEL; + case 0xF8: + return base::VKEY_EXSEL; + case 0xF9: + return base::VKEY_EREOF; + case 0xFA: + return base::VKEY_PLAY; + case 0xFB: + return base::VKEY_ZOOM; + case 0xFC: + return base::VKEY_NONAME; + case 0xFD: + return base::VKEY_PA1; + case 0xFE: + return base::VKEY_OEM_CLEAR; + default: + return base::VKEY_UNKNOWN; + } +} + +EventExecutorWin::EventExecutorWin() { +} + +EventExecutorWin::~EventExecutorWin() { +} + +void EventExecutorWin::HandleInputEvents(ClientMessageList* messages) { + for (size_t i = 0; i < messages->size(); ++i) { + chromotocol_pb::ClientMessage* msg = (*messages)[i]; + if (msg->has_mouse_set_position_event()) { + mouse_event(MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE, + static_cast<int>((msg->mouse_set_position_event().x() * 65535)), + static_cast<int>((msg->mouse_set_position_event().y() * 65535)), + 0, 0); + } else if (msg->has_mouse_move_event()) { + mouse_event(MOUSEEVENTF_MOVE, + msg->mouse_move_event().offset_x(), + msg->mouse_move_event().offset_y(), 0, 0); + } else if (msg->has_mouse_wheel_event()) { + // TODO(hclam): Handle wheel events. + } else if (msg->has_mouse_down_event()) { + if (msg->mouse_down_event().button() == + chromotocol_pb::MouseDownEvent::LEFT) { + mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0); + } else if (msg->mouse_down_event().button() == + chromotocol_pb::MouseDownEvent::RIGHT) { + mouse_event(MOUSEEVENTF_RIGHTDOWN, 0, 0, 0, 0); + } else { + // TODO(hclam): Handle other buttons. + } + } else if (msg->has_mouse_up_event()) { + if (msg->mouse_up_event().button() == + chromotocol_pb::MouseUpEvent::LEFT) { + mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0); + } else if (msg->mouse_up_event().button() == + chromotocol_pb::MouseUpEvent::RIGHT) { + mouse_event(MOUSEEVENTF_RIGHTUP, 0, 0, 0, 0); + } else { + // TODO(hclam): Handle other buttons. + } + } else if (msg->has_key_event()) { + base::KeyboardCode key_code = + WindowsKeyCodeForPosixKeyCode(msg->key_event().key()); + if (key_code != base::VKEY_UNKNOWN) { + keybd_event(key_code, MapVirtualKey(key_code, 0), + msg->key_event().pressed() ? 0 : KEYEVENTF_KEYUP, + NULL); + } + } + } + // We simply delete all messages. + // TODO(hclam): Delete messages processed. + STLDeleteElements<ClientMessageList>(messages); +} + +} // namespace remoting diff --git a/remoting/host/event_executor_win.h b/remoting/host/event_executor_win.h new file mode 100644 index 0000000..53e790f --- /dev/null +++ b/remoting/host/event_executor_win.h @@ -0,0 +1,28 @@ +// 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. + +#ifndef REMOTING_HOST_EVENT_EXECUTOR_WIN_H_ +#define REMOTING_HOST_EVENT_EXECUTOR_WIN_H_ + +#include <vector> + +#include "remoting/host/event_executor.h" + +namespace remoting { + +// A class to generate events on Windows. +class EventExecutorWin : public EventExecutor { + public: + EventExecutorWin(); + virtual ~EventExecutorWin(); + + virtual void HandleInputEvents(ClientMessageList* messages); + + private: + DISALLOW_COPY_AND_ASSIGN(EventExecutorWin); +}; + +} // namespace remoting + +#endif // REMOTING_HOST_EVENT_EXECUTOR_WIN_H_ diff --git a/remoting/host/heartbeat_sender.cc b/remoting/host/heartbeat_sender.cc new file mode 100644 index 0000000..e72c5a1 --- /dev/null +++ b/remoting/host/heartbeat_sender.cc @@ -0,0 +1,69 @@ +// 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/heartbeat_sender.h" + +#include "base/logging.h" +#include "base/message_loop.h" +#include "remoting/base/constants.h" +#include "remoting/jingle_glue/iq_request.h" +#include "remoting/jingle_glue/jingle_client.h" +#include "remoting/jingle_glue/jingle_thread.h" +#include "talk/xmpp/constants.h" +#include "talk/xmllite/xmlelement.h" + +namespace remoting { + +namespace { +const char * const kChromotingNamespace = "google:remoting"; +const buzz::QName kHeartbeatQuery(true, kChromotingNamespace, "heartbeat"); +const buzz::QName kHostIdAttr(true, kChromotingNamespace, "hostid"); + +// TODO(sergeyu): Make this configurable by the cloud. +const int64 kHeartbeatPeriodMs = 5 * 60 * 1000; // 5 minutes. +} + +HeartbeatSender::HeartbeatSender() + : started_(false) { +} + +void HeartbeatSender::Start(JingleClient* jingle_client, + const std::string& host_id) { + DCHECK(jingle_client); + DCHECK(!started_); + + started_ = true; + + jingle_client_ = jingle_client; + host_id_ = host_id; + + jingle_client_->message_loop()->PostTask( + FROM_HERE, NewRunnableMethod(this, &HeartbeatSender::DoStart)); +} + +void HeartbeatSender::DoStart() { + DCHECK(MessageLoop::current() == jingle_client_->message_loop()); + + request_.reset(new IqRequest(jingle_client_)); + + jingle_client_->message_loop()->PostTask( + FROM_HERE, NewRunnableMethod(this, &HeartbeatSender::DoSendStanza)); +} + +void HeartbeatSender::DoSendStanza() { + DCHECK(MessageLoop::current() == jingle_client_->message_loop()); + + LOG(INFO) << "Sending heartbeat stanza to " << kChromotingBotJid; + + buzz::XmlElement* stanza = new buzz::XmlElement(kHeartbeatQuery); + stanza->AddAttr(kHostIdAttr, host_id_); + request_->SendIq(buzz::STR_SET, kChromotingBotJid, stanza); + + // Schedule next heartbeat. + jingle_client_->message_loop()->PostDelayedTask( + FROM_HERE, NewRunnableMethod(this, &HeartbeatSender::DoSendStanza), + kHeartbeatPeriodMs); +} + +} // namespace remoting diff --git a/remoting/host/heartbeat_sender.h b/remoting/host/heartbeat_sender.h new file mode 100644 index 0000000..b072511 --- /dev/null +++ b/remoting/host/heartbeat_sender.h @@ -0,0 +1,41 @@ +// 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. + +#ifndef REMOTING_HOST_HEARTBEAT_SENDER_H_ +#define REMOTING_HOST_HEARTBEAT_SENDER_H_ + +#include <string> + +#include "base/scoped_ptr.h" +#include "base/ref_counted.h" +#include "remoting/jingle_glue/iq_request.h" + +namespace remoting { + +class IqRequest; +class JingleClient; + +// HeartbeatSender periodically sends hertbeats to the chromoting bot. +// TODO(sergeyu): Write unittest for this class. +class HeartbeatSender : public base::RefCountedThreadSafe<HeartbeatSender> { + public: + HeartbeatSender(); + + // Starts heart-beating for |jingle_client|. + void Start(JingleClient* jingle_client, const std::string& host_id); + + private: + + void DoStart(); + void DoSendStanza(); + + bool started_; + JingleClient* jingle_client_; + std::string host_id_; + scoped_ptr<IqRequest> request_; +}; + +} // namespace remoting + +#endif // REMOTING_HOST_HEARTBEAT_SENDER_H_ diff --git a/remoting/host/mock_objects.h b/remoting/host/mock_objects.h new file mode 100644 index 0000000..1f33263 --- /dev/null +++ b/remoting/host/mock_objects.h @@ -0,0 +1,103 @@ +// 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. + +#ifndef REMOTING_HOST_MOCK_OBJECTS_H_ +#define REMOTING_HOST_MOCK_OBJECTS_H_ + +#include "media/base/data_buffer.h" +#include "remoting/base/protocol_decoder.h" +#include "remoting/host/capturer.h" +#include "remoting/host/client_connection.h" +#include "remoting/host/encoder.h" +#include "remoting/host/event_executor.h" +#include "testing/gmock/include/gmock/gmock.h" + +namespace remoting { + +class MockCapturer : public Capturer { + public: + MockCapturer() {} + + MOCK_METHOD1(CaptureFullScreen, void(Task* done_task)); + MOCK_METHOD1(CaptureDirtyRects, void(Task* done_task)); + MOCK_METHOD2(CaptureRect, void(const gfx::Rect& rect, Task* done_task)); + MOCK_CONST_METHOD1(GetData, void(const uint8* planes[])); + MOCK_CONST_METHOD1(GetDataStride, void(int strides[])); + MOCK_CONST_METHOD1(GetDirtyRects, void(DirtyRects* rects)); + MOCK_CONST_METHOD0(GetWidth, int()); + MOCK_CONST_METHOD0(GetHeight, int()); + MOCK_CONST_METHOD0(GetPixelFormat, chromotocol_pb::PixelFormat()); + + private: + DISALLOW_COPY_AND_ASSIGN(MockCapturer); +}; + +class MockEncoder : public Encoder { + public: + MockEncoder() {} + + MOCK_METHOD8(Encode, void( + const DirtyRects& dirty_rects, + const uint8** planes, + const int* strides, + bool key_frame, + chromotocol_pb::UpdateStreamPacketHeader* output_data_header, + scoped_refptr<media::DataBuffer>* output_data, + bool* encode_done, + Task* data_available_task)); + MOCK_METHOD2(SetSize, void(int width, int height)); + MOCK_METHOD1(SetPixelFormat, void(chromotocol_pb::PixelFormat pixel_format)); + + private: + DISALLOW_COPY_AND_ASSIGN(MockEncoder); +}; + +class MockEventExecutor : public EventExecutor { + public: + MockEventExecutor() {} + + MOCK_METHOD1(HandleInputEvents, void(ClientMessageList* messages)); + + private: + DISALLOW_COPY_AND_ASSIGN(MockEventExecutor); +}; + +class MockClientConnection : public ClientConnection { + public: + MockClientConnection(){} + + MOCK_METHOD2(SendInitClientMessage, void(int width, int height)); + MOCK_METHOD0(SendBeginUpdateStreamMessage, void()); + MOCK_METHOD2(SendUpdateStreamPacketMessage, + void(chromotocol_pb::UpdateStreamPacketHeader* header, + scoped_refptr<media::DataBuffer> data)); + MOCK_METHOD0(SendEndUpdateStreamMessage, void()); + MOCK_METHOD0(GetPendingUpdateStreamMessages, int()); + + MOCK_METHOD2(OnStateChange, void(JingleChannel* channel, + JingleChannel::State state)); + MOCK_METHOD2(OnPacketReceived, void(JingleChannel* channel, + scoped_refptr<media::DataBuffer> data)); + + private: + DISALLOW_COPY_AND_ASSIGN(MockClientConnection); +}; + +class MockClientConnectionEventHandler : public ClientConnection::EventHandler { + public: + MockClientConnectionEventHandler() {} + + MOCK_METHOD2(HandleMessages, + void(ClientConnection* viewer, ClientMessageList* messages)); + MOCK_METHOD1(OnConnectionOpened, void(ClientConnection* viewer)); + MOCK_METHOD1(OnConnectionClosed, void(ClientConnection* viewer)); + MOCK_METHOD1(OnConnectionFailed, void(ClientConnection* viewer)); + + private: + DISALLOW_COPY_AND_ASSIGN(MockClientConnectionEventHandler); +}; + +} // namespace remoting + +#endif // REMOTING_HOST_MOCK_OBJECTS_H_ diff --git a/remoting/host/session_manager.cc b/remoting/host/session_manager.cc new file mode 100644 index 0000000..da16a55 --- /dev/null +++ b/remoting/host/session_manager.cc @@ -0,0 +1,401 @@ +// 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/session_manager.h" + +#include <algorithm> + +#include "base/logging.h" +#include "base/stl_util-inl.h" +#include "media/base/data_buffer.h" +#include "remoting/base/protocol_decoder.h" +#include "remoting/host/client_connection.h" +#include "remoting/host/encoder.h" + +namespace remoting { + +// By default we capture 20 times a second. This number is obtained by +// experiment to provide good latency. +static const double kDefaultCaptureRate = 20.0; + +// Interval that we perform rate regulation. +static const base::TimeDelta kRateControlInterval = + base::TimeDelta::FromSeconds(1); + +// We divide the pending update stream number by this value to determine the +// rate divider. +static const int kSlowDownFactor = 10; + +// A list of dividers used to divide the max rate to determine the current +// capture rate. +static const int kRateDividers[] = {1, 2, 4, 8, 16}; + +SessionManager::SessionManager( + MessageLoop* capture_loop, + MessageLoop* encode_loop, + MessageLoop* network_loop, + Capturer* capturer, + Encoder* encoder) + : capture_loop_(capture_loop), + encode_loop_(encode_loop), + network_loop_(network_loop), + capturer_(capturer), + encoder_(encoder), + rate_(kDefaultCaptureRate), + max_rate_(kDefaultCaptureRate), + started_(false), + recordings_(0), + rate_control_started_(false), + capture_width_(0), + capture_height_(0), + capture_pixel_format_(chromotocol_pb::PixelFormatInvalid), + encode_stream_started_(false), + encode_done_(false) { + DCHECK(capture_loop_); + DCHECK(encode_loop_); + DCHECK(network_loop_); +} + +SessionManager::~SessionManager() { + clients_.clear(); + DCHECK_EQ(0u, clients_.size()); +} + +void SessionManager::Start() { + capture_loop_->PostTask( + FROM_HERE, NewRunnableMethod(this, &SessionManager::DoStart)); +} + +void SessionManager::DoStart() { + DCHECK_EQ(capture_loop_, MessageLoop::current()); + + if (started_) { + NOTREACHED() << "Record session already started"; + return; + } + + started_ = true; + DoCapture(); + + // Starts the rate regulation. + network_loop_->PostTask( + FROM_HERE, + NewRunnableMethod(this, &SessionManager::DoStartRateControl)); +} + +void SessionManager::DoStartRateControl() { + DCHECK_EQ(network_loop_, MessageLoop::current()); + + if (rate_control_started_) { + NOTREACHED() << "Rate regulation already started"; + return; + } + rate_control_started_ = true; + ScheduleNextRateControl(); +} + +void SessionManager::Pause() { + capture_loop_->PostTask( + FROM_HERE, NewRunnableMethod(this, &SessionManager::DoPause)); +} + +void SessionManager::DoPause() { + DCHECK_EQ(capture_loop_, MessageLoop::current()); + + if (!started_) { + NOTREACHED() << "Record session not started"; + return; + } + + started_ = false; + + // Pause the rate regulation. + network_loop_->PostTask( + FROM_HERE, + NewRunnableMethod(this, &SessionManager::DoPauseRateControl)); +} + +void SessionManager::DoPauseRateControl() { + DCHECK_EQ(network_loop_, MessageLoop::current()); + + if (!rate_control_started_) { + NOTREACHED() << "Rate regulation not started"; + return; + } + rate_control_started_ = false; +} + +void SessionManager::SetMaxRate(double rate) { + capture_loop_->PostTask( + FROM_HERE, NewRunnableMethod(this, &SessionManager::DoSetMaxRate, rate)); +} + +void SessionManager::AddClient(scoped_refptr<ClientConnection> client) { + network_loop_->PostTask( + FROM_HERE, + NewRunnableMethod(this, &SessionManager::DoAddClient, client)); +} + +void SessionManager::RemoveClient(scoped_refptr<ClientConnection> client) { + network_loop_->PostTask( + FROM_HERE, + NewRunnableMethod(this, &SessionManager::DoRemoveClient, client)); +} + +void SessionManager::DoCapture() { + DCHECK_EQ(capture_loop_, MessageLoop::current()); + + // Make sure we have at most two oustanding recordings. We can simply return + // if we can't make a capture now, the next capture will be started by the + // end of an encode operation. + if (recordings_ >= 2 || !started_) + return; + + base::Time now = base::Time::Now(); + base::TimeDelta interval = base::TimeDelta::FromMilliseconds( + static_cast<int>(base::Time::kMillisecondsPerSecond / rate_)); + base::TimeDelta elapsed = now - last_capture_time_; + + // If this method is called sonner than the required interval we return + // immediately + if (elapsed < interval) + return; + + // At this point we are going to perform one capture so save the current time. + last_capture_time_ = now; + ++recordings_; + + // Before we actually do a capture, schedule the next one. + ScheduleNextCapture(); + + // And finally perform one capture. + DCHECK(capturer_.get()); + capturer_->CaptureDirtyRects( + NewRunnableMethod(this, &SessionManager::CaptureDoneTask)); +} + +void SessionManager::DoFinishEncode() { + DCHECK_EQ(capture_loop_, MessageLoop::current()); + + // Decrement the number of recording in process since we have completed + // one cycle. + --recordings_; + + // Try to do a capture again. Note that the following method may do nothing + // if it is too early to perform a capture. + if (rate_ > 0) + DoCapture(); +} + +void SessionManager::DoEncode() { + DCHECK_EQ(encode_loop_, MessageLoop::current()); + + // Reset states about the encode stream. + encode_done_ = false; + encode_stream_started_ = false; + + DCHECK(!encoded_data_.get()); + DCHECK(encoder_.get()); + + // TODO(hclam): Enable |force_refresh| if a new client was + // added. + encoder_->SetSize(capture_width_, capture_height_); + encoder_->SetPixelFormat(capture_pixel_format_); + encoder_->Encode( + capture_dirty_rects_, + capture_data_, + capture_data_strides_, + false, + &encoded_data_header_, + &encoded_data_, + &encode_done_, + NewRunnableMethod(this, &SessionManager::EncodeDataAvailableTask)); +} + +void SessionManager::DoSendUpdate( + chromotocol_pb::UpdateStreamPacketHeader* header, + scoped_refptr<media::DataBuffer> encoded_data, + bool begin_update, bool end_update) { + DCHECK_EQ(network_loop_, MessageLoop::current()); + + for (size_t i = 0; i < clients_.size(); ++i) { + if (begin_update) + clients_[i]->SendBeginUpdateStreamMessage(); + + // This will pass the ownership of the DataBuffer to the ClientConnection. + clients_[i]->SendUpdateStreamPacketMessage(header, encoded_data); + + if (end_update) + clients_[i]->SendEndUpdateStreamMessage(); + } +} + +void SessionManager::DoSendInit(scoped_refptr<ClientConnection> client, + int width, int height) { + DCHECK_EQ(network_loop_, MessageLoop::current()); + + // Sends the client init information. + client->SendInitClientMessage(width, height); +} + +void SessionManager::DoGetInitInfo(scoped_refptr<ClientConnection> client) { + DCHECK_EQ(capture_loop_, MessageLoop::current()); + + network_loop_->PostTask( + FROM_HERE, + NewRunnableMethod(this, &SessionManager::DoSendInit, client, + capturer_->GetWidth(), capturer_->GetHeight())); +} + +void SessionManager::DoSetRate(double rate) { + DCHECK_EQ(capture_loop_, MessageLoop::current()); + if (rate == rate_) + return; + + // Change the current capture rate. + rate_ = rate; + + // If we have already started then schedule the next capture with the new + // rate. + if (started_) + ScheduleNextCapture(); +} + +void SessionManager::DoSetMaxRate(double max_rate) { + DCHECK_EQ(capture_loop_, MessageLoop::current()); + + // TODO(hclam): Should also check for small epsilon. + if (max_rate != 0) { + max_rate_ = max_rate; + DoSetRate(max_rate); + } else { + NOTREACHED() << "Rate is too small."; + } +} + +void SessionManager::DoAddClient(scoped_refptr<ClientConnection> client) { + DCHECK_EQ(network_loop_, MessageLoop::current()); + + // TODO(hclam): Force a full frame for next encode. + clients_.push_back(client); + + // Gets the init information for the client. + capture_loop_->PostTask( + FROM_HERE, + NewRunnableMethod(this, &SessionManager::DoGetInitInfo, client)); +} + +void SessionManager::DoRemoveClient(scoped_refptr<ClientConnection> client) { + DCHECK_EQ(network_loop_, MessageLoop::current()); + + // TODO(hclam): Is it correct to do to a scoped_refptr? + ClientConnectionList::iterator it + = std::find(clients_.begin(), clients_.end(), client); + if (it != clients_.end()) + clients_.erase(it); +} + +void SessionManager::DoRateControl() { + DCHECK_EQ(network_loop_, MessageLoop::current()); + + // If we have been paused then shutdown the rate regulation loop. + if (!rate_control_started_) + return; + + int max_pending_update_streams = 0; + for (size_t i = 0; i < clients_.size(); ++i) { + max_pending_update_streams = + std::max(max_pending_update_streams, + clients_[i]->GetPendingUpdateStreamMessages()); + } + + // If |slow_down| equals zero, we have no slow down. + int slow_down = max_pending_update_streams / kSlowDownFactor; + // Set new_rate to -1 for checking later. + double new_rate = -1; + // If the slow down is too large. + if (slow_down >= arraysize(kRateDividers)) { + // Then we stop the capture completely. + new_rate = 0; + } else { + // Slow down the capture rate using the divider. + new_rate = max_rate_ / kRateDividers[slow_down]; + } + DCHECK_NE(new_rate, -1.0); + + // Then set the rate. + capture_loop_->PostTask( + FROM_HERE, + NewRunnableMethod(this, &SessionManager::DoSetRate, new_rate)); + ScheduleNextRateControl(); +} + +void SessionManager::ScheduleNextCapture() { + DCHECK_EQ(capture_loop_, MessageLoop::current()); + + if (rate_ == 0) + return; + + base::TimeDelta interval = base::TimeDelta::FromMilliseconds( + static_cast<int>(base::Time::kMillisecondsPerSecond / rate_)); + capture_loop_->PostDelayedTask( + FROM_HERE, + NewRunnableMethod(this, &SessionManager::DoCapture), + interval.InMilliseconds()); +} + +void SessionManager::ScheduleNextRateControl() { + network_loop_->PostDelayedTask( + FROM_HERE, + NewRunnableMethod(this, &SessionManager::DoRateControl), + kRateControlInterval.InMilliseconds()); +} + +void SessionManager::CaptureDoneTask() { + DCHECK_EQ(capture_loop_, MessageLoop::current()); + + // Save results of the capture. + capturer_->GetData(capture_data_); + capturer_->GetDataStride(capture_data_strides_); + capture_dirty_rects_.clear(); + capturer_->GetDirtyRects(&capture_dirty_rects_); + capture_pixel_format_ = capturer_->GetPixelFormat(); + capture_width_ = capturer_->GetWidth(); + capture_height_ = capturer_->GetHeight(); + + encode_loop_->PostTask( + FROM_HERE, NewRunnableMethod(this, &SessionManager::DoEncode)); +} + +void SessionManager::EncodeDataAvailableTask() { + DCHECK_EQ(encode_loop_, MessageLoop::current()); + + // Before a new encode task starts, notify clients a new update + // stream is coming. + // Notify this will keep a reference to the DataBuffer in the + // task. The ownership will eventually pass to the ClientConnections. + network_loop_->PostTask( + FROM_HERE, + NewRunnableMethod(this, + &SessionManager::DoSendUpdate, + &encoded_data_header_, + encoded_data_, + !encode_stream_started_, + encode_done_)); + + // Since we have received data from the Encoder, mark the encode + // stream has started. + encode_stream_started_ = true; + + // Give up the ownership of DataBuffer since it is passed to + // the ClientConnections. + encoded_data_ = NULL; + + if (encode_done_) { + capture_loop_->PostTask( + FROM_HERE, NewRunnableMethod(this, &SessionManager::DoFinishEncode)); + } +} + +} // namespace remoting diff --git a/remoting/host/session_manager.h b/remoting/host/session_manager.h new file mode 100644 index 0000000..2a88020 --- /dev/null +++ b/remoting/host/session_manager.h @@ -0,0 +1,190 @@ +// 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. + +#ifndef REMOTING_HOST_RECORD_SESSION_H_ +#define REMOTING_HOST_RECORD_SESSION_H_ + +#include <vector> + +#include "base/basictypes.h" +#include "base/message_loop.h" +#include "base/ref_counted.h" +#include "base/scoped_ptr.h" +#include "base/time.h" +#include "remoting/base/protocol/chromotocol.pb.h" +#include "remoting/host/capturer.h" + +namespace media { + +class DataBuffer; + +} // namespace media + +namespace remoting { + +class Encoder; +class ClientConnection; + +// A class for controlling and coordinate Capturer, Encoder +// and NetworkChannel in a record session. +// +// THREADING +// +// This class works on three threads, namely capture, encode and network +// thread. The main function of this class is to coordinate and schedule +// capture, encode and transmission of data on different threads. +// +// The following is an example of timeline for operations scheduled. +// +// | CAPTURE ENCODE NETWORK +// | ............. +// | . Capture . +// | ............. +// | ............ +// | . . +// | ............. . . +// | . Capture . . Encode . +// | ............. . . +// | . . +// | ............ +// | ............. ............ .......... +// | . Capture . . . . Send . +// | ............. . . .......... +// | . Encode . +// | . . +// | . . +// | ............ +// | Time +// v +// +// SessionManager has the following responsibilities: +// 1. Make sure capture and encode occurs no more frequently than |rate|. +// 2. Make sure there is at most one outstanding capture not being encoded. +// 3. Distribute tasks on three threads on a timely fashion to minimize latency. +class SessionManager : public base::RefCountedThreadSafe<SessionManager> { + public: + + // Construct a SessionManager. Message loops and threads are provided. + // Ownership of Capturer and Encoder are given to this object. + SessionManager(MessageLoop* capture_loop, + MessageLoop* encode_loop, + MessageLoop* network_loop, + Capturer* capturer, + Encoder* encoder); + + virtual ~SessionManager(); + + // Start recording. + void Start(); + + // Pause the recording session. + void Pause(); + + // Set the maximum capture rate. This is denoted by number of updates + // in one second. The actual system may run in a slower rate than the maximum + // rate due to various factors, e.g. capture speed, encode speed and network + // conditions. + // This method should be called before Start() is called. + void SetMaxRate(double rate); + + // Add a client to this recording session. + void AddClient(scoped_refptr<ClientConnection> client); + + // Remove a client from receiving screen updates. + void RemoveClient(scoped_refptr<ClientConnection> client); + + private: + void DoStart(); + void DoPause(); + void DoStartRateControl(); + void DoPauseRateControl(); + + void DoCapture(); + void DoFinishEncode(); + void DoEncode(); + void DoSendUpdate( + chromotocol_pb::UpdateStreamPacketHeader* header, + scoped_refptr<media::DataBuffer> encoded_data, + bool begin_update, + bool end_update); + void DoSendInit(scoped_refptr<ClientConnection> client, + int width, int height); + void DoGetInitInfo(scoped_refptr<ClientConnection> client); + void DoSetRate(double rate); + void DoSetMaxRate(double max_rate); + void DoAddClient(scoped_refptr<ClientConnection> client); + void DoRemoveClient(scoped_refptr<ClientConnection> client); + void DoRateControl(); + + // Hepler method to schedule next capture using the current rate. + void ScheduleNextCapture(); + + // Helper method to schedule next rate regulation task. + void ScheduleNextRateControl(); + + void CaptureDoneTask(); + void EncodeDataAvailableTask(); + + // Message loops used by this class. + MessageLoop* capture_loop_; + MessageLoop* encode_loop_; + MessageLoop* network_loop_; + + // Reference to the capturer. This member is always accessed on the capture + // thread. + scoped_ptr<Capturer> capturer_; + + // Reference to the encoder. This member is always accessed on the encode + // thread. + scoped_ptr<Encoder> encoder_; + + // A list of clients connected to this hosts. + // This member is always accessed on the NETWORK thread. + // TODO(hclam): Have to scoped_refptr the clients since they have a shorter + // lifetime than this object. + typedef std::vector<scoped_refptr<ClientConnection> > ClientConnectionList; + ClientConnectionList clients_; + + // The following members are accessed on the capture thread. + double rate_; // Number of captures to perform every second. + bool started_; + base::Time last_capture_time_; // Saves the time last capture started. + int recordings_; // Count the number of recordings + // (i.e. capture or encode) happening. + + // The maximum rate is written on the capture thread and read on the network + // thread. + double max_rate_; // Number of captures to perform every second. + + // The following member is accessed on the network thread. + bool rate_control_started_; + + // Stores the data and information of the last capture done. + // These members are written on capture thread and read on encode thread. + // It is guranteed the read happens after the write. + DirtyRects capture_dirty_rects_; + const uint8* capture_data_[3]; + int capture_data_strides_[3]; + int capture_width_; + int capture_height_; + chromotocol_pb::PixelFormat capture_pixel_format_; + + // The following members are accessed on the encode thread. + // Output parameter written by Encoder to carry encoded data. + chromotocol_pb::UpdateStreamPacketHeader encoded_data_header_; + scoped_refptr<media::DataBuffer> encoded_data_; + + // True if we have started receiving encoded data from the Encoder. + bool encode_stream_started_; + + // Output parameter written by Encoder to notify the end of encoded data + // stream. + bool encode_done_; + + DISALLOW_COPY_AND_ASSIGN(SessionManager); +}; + +} // namespace remoting + +#endif // REMOTING_HOST_RECORD_SESSION_H_ diff --git a/remoting/host/session_manager_unittest.cc b/remoting/host/session_manager_unittest.cc new file mode 100644 index 0000000..9f4cdea --- /dev/null +++ b/remoting/host/session_manager_unittest.cc @@ -0,0 +1,139 @@ +// 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 "base/message_loop.h" +#include "base/task.h" +#include "remoting/host/mock_objects.h" +#include "remoting/host/session_manager.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::_; +using ::testing::AtLeast; +using ::testing::NotNull; +using ::testing::Return; + +namespace remoting { + +static const int kWidth = 640; +static const int kHeight = 480; +static int kStride[3] = { + kWidth * 4, + kWidth * 4, + kWidth * 4, +}; +static uint8* kData[3] = { + reinterpret_cast<uint8*>(0x01), + reinterpret_cast<uint8*>(0x02), + reinterpret_cast<uint8*>(0x03), +}; +static const chromotocol_pb::PixelFormat kFormat = + chromotocol_pb::PixelFormatRgb32; + +class SessionManagerTest : public testing::Test { + public: + SessionManagerTest() { + } + + protected: + void Init() { + capturer_ = new MockCapturer(); + encoder_ = new MockEncoder(); + client_ = new MockClientConnection(); + record_ = new SessionManager(&message_loop_, + &message_loop_, + &message_loop_, + capturer_, + encoder_); + } + + scoped_refptr<SessionManager> record_; + scoped_refptr<MockClientConnection> client_; + MockCapturer* capturer_; + MockEncoder* encoder_; + MessageLoop message_loop_; + + private: + DISALLOW_COPY_AND_ASSIGN(SessionManagerTest); +}; + +TEST_F(SessionManagerTest, Init) { + Init(); +} + +ACTION(RunSimpleTask) { + arg0->Run(); + delete arg0; +} + +ACTION_P2(FinishDecode, header, data) { + *arg4 = header; + *arg5 = data; + *arg6 = true; + arg7->Run(); + delete arg7; +} + +ACTION_P(AssignCaptureData, data) { + arg0[0] = data[0]; + arg0[1] = data[1]; + arg0[2] = data[2]; +} + +TEST_F(SessionManagerTest, OneRecordCycle) { + Init(); + + // Set the recording rate to very low to avoid capture twice. + record_->SetMaxRate(0.01); + + // Add the mock client connection to the session. + EXPECT_CALL(*capturer_, GetWidth()).WillRepeatedly(Return(kWidth)); + EXPECT_CALL(*capturer_, GetHeight()).WillRepeatedly(Return(kHeight)); + EXPECT_CALL(*client_, SendInitClientMessage(kWidth, kHeight)); + record_->AddClient(client_); + + // First the capturer is called. + EXPECT_CALL(*capturer_, CaptureDirtyRects(NotNull())) + .WillOnce(RunSimpleTask()); + // TODO(hclam): Return DirtyRects for verification. + EXPECT_CALL(*capturer_, GetDirtyRects(NotNull())); + EXPECT_CALL(*capturer_, GetData(NotNull())) + .WillOnce(AssignCaptureData(kData)); + EXPECT_CALL(*capturer_, GetDataStride(NotNull())) + .WillOnce(AssignCaptureData(kStride)); + EXPECT_CALL(*capturer_, GetPixelFormat()) + .WillOnce(Return(kFormat)); + + // Expect the encoder be called. + chromotocol_pb::UpdateStreamPacketHeader header; + scoped_refptr<media::DataBuffer> buffer = new media::DataBuffer(0); + EXPECT_CALL(*encoder_, SetSize(kWidth, kHeight)); + EXPECT_CALL(*encoder_, SetPixelFormat(kFormat)); + // TODO(hclam): Expect the content of the dirty rects. + EXPECT_CALL(*encoder_, + Encode(_, NotNull(), NotNull(), false, NotNull(), + NotNull(), NotNull(), NotNull())) + .WillOnce(FinishDecode(header, buffer)); + + // Expect the client be notified. + EXPECT_CALL(*client_, SendBeginUpdateStreamMessage()); + EXPECT_CALL(*client_, SendUpdateStreamPacketMessage(NotNull(), buffer)); + EXPECT_CALL(*client_, SendEndUpdateStreamMessage()); + EXPECT_CALL(*client_, GetPendingUpdateStreamMessages()) + .Times(AtLeast(0)) + .WillRepeatedly(Return(0)); + + + // Start the recording. + record_->Start(); + + // Make sure all tasks are completed. + message_loop_.RunAllPending(); +} + +// TODO(hclam): Add test for double buffering. +// TODO(hclam): Add test for multiple captures. +// TODO(hclam): Add test for interruption. + +} // namespace remoting diff --git a/remoting/host/simple_host.cc b/remoting/host/simple_host.cc new file mode 100644 index 0000000..945b663 --- /dev/null +++ b/remoting/host/simple_host.cc @@ -0,0 +1,201 @@ +// 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/simple_host.h" + +#include "base/stl_util-inl.h" +#include "build/build_config.h" +#include "remoting/base/protocol_decoder.h" +#include "remoting/host/session_manager.h" +#include "remoting/jingle_glue/jingle_channel.h" + +namespace remoting { + +SimpleHost::SimpleHost(const std::string& username, + const std::string& password, + Capturer* capturer, + Encoder* encoder, + EventExecutor* executor) + : capture_thread_("CaptureThread"), + encode_thread_("EncodeThread"), + username_(username), + password_(password), + capturer_(capturer), + encoder_(encoder), + executor_(executor) { +} + +void SimpleHost::Run() { + DCHECK_EQ(&main_loop_, MessageLoop::current()); + + // Submit a task to perform host registration. We'll also start + // listening to connection if registration is done. + RegisterHost(); + + // Run the main message loop. This is the main loop of this host + // object. + main_loop_.Run(); +} + +// This method is called when we need to the host process. +void SimpleHost::DestroySession() { + DCHECK_EQ(&main_loop_, MessageLoop::current()); + + // First we tell the session to pause and then we wait until all + // the tasks are done. + session_->Pause(); + + // TODO(hclam): Revise the order. + encode_thread_.Stop(); + capture_thread_.Stop(); +} + +// This method talks to the cloud to register the host process. If +// successful we will start listening to network requests. +void SimpleHost::RegisterHost() { + DCHECK_EQ(&main_loop_, MessageLoop::current()); + DCHECK(!jingle_client_); + + // Connect to the talk network with a JingleClient. + jingle_client_ = new JingleClient(); + jingle_client_->Init(username_, password_, this); +} + +// This method is called if a client is connected to this object. +void SimpleHost::OnClientConnected(ClientConnection* client) { + DCHECK_EQ(&main_loop_, MessageLoop::current()); + + // Create a new RecordSession if there was none. + if (!session_) { + // The first we need to make sure capture and encode thread are + // running. + capture_thread_.Start(); + encode_thread_.Start(); + + // Then we create a SessionManager passing the message loops that + // it should run on. + // Note that we pass the ownership of the capturer and encoder to + // the session manager. + DCHECK(capturer_.get()); + DCHECK(encoder_.get()); + session_ = new SessionManager(capture_thread_.message_loop(), + encode_thread_.message_loop(), + &main_loop_, + capturer_.release(), + encoder_.release()); + + // Immediately add the client and start the session. + session_->AddClient(client); + session_->Start(); + LOG(INFO) << "Session manager started"; + } else { + // If a session manager already exists we simply add the new client. + session_->AddClient(client); + } +} + +void SimpleHost::OnClientDisconnected(ClientConnection* client) { + DCHECK_EQ(&main_loop_, MessageLoop::current()); + + // Remove the client from the session manager. + DCHECK(session_); + session_->RemoveClient(client); + + // Also remove reference to ClientConnection from this object. + client_ = NULL; + + // TODO(hclam): If the last client has disconnected we need destroy + // the session manager and shutdown the capture and encode threads. + // Right now we assume there's only one client. + DestroySession(); +} + +//////////////////////////////////////////////////////////////////////////// +// ClientConnection::EventHandler implementations +void SimpleHost::HandleMessages(ClientConnection* client, + ClientMessageList* messages) { + DCHECK_EQ(&main_loop_, MessageLoop::current()); + + // Delegate the messages to EventExecutor and delete the unhandled + // messages. + DCHECK(executor_.get()); + executor_->HandleInputEvents(messages); + STLDeleteElements<ClientMessageList>(messages); +} + +void SimpleHost::OnConnectionOpened(ClientConnection* client) { + DCHECK_EQ(&main_loop_, MessageLoop::current()); + + // Completes the client connection. + LOG(INFO) << "Connection to client established."; + OnClientConnected(client_.get()); +} + +void SimpleHost::OnConnectionClosed(ClientConnection* client) { + DCHECK_EQ(&main_loop_, MessageLoop::current()); + + // Completes the client connection. + LOG(INFO) << "Connection to client closed."; + OnClientDisconnected(client_.get()); +} + +void SimpleHost::OnConnectionFailed(ClientConnection* client) { + DCHECK_EQ(&main_loop_, MessageLoop::current()); + + // The client has disconnected. + LOG(ERROR) << "Connection failed unexpectedly."; + OnClientDisconnected(client_.get()); +} + +//////////////////////////////////////////////////////////////////////////// +// JingleClient::Callback implementations +void SimpleHost::OnStateChange(JingleClient* jingle_client, + JingleClient::State state) { + DCHECK_EQ(jingle_client_.get(), jingle_client); + + if (state == JingleClient::CONNECTED) { + // TODO(hclam): Change to use LOG(INFO). + // LOG(INFO) << "Host connected as " + // << jingle_client->GetFullJid() << "." << std::endl; + printf("Host connected as %s\n", jingle_client->GetFullJid().c_str()); + + // Start heartbeating after we connected + heartbeat_sender_ = new HeartbeatSender(); + // TODO(sergeyu): where do we get host id? + heartbeat_sender_->Start(jingle_client_.get(), "HostID"); + } else if (state == JingleClient::CLOSED) { + LOG(INFO) << "Host disconnected from talk network." << std::endl; + + heartbeat_sender_ = NULL; + } +} + +bool SimpleHost::OnAcceptConnection( + JingleClient* jingle_client, const std::string& jid, + JingleChannel::Callback** channel_callback) { + DCHECK_EQ(jingle_client_.get(), jingle_client); + + if (client_.get()) + return false; + + LOG(INFO) << "Client connected: " << jid << std::endl; + + // If we accept the connected then create a client object and set the + // callback. + client_ = new ClientConnection(&main_loop_, new ProtocolDecoder(), this); + *channel_callback = client_.get(); + return true; +} + +void SimpleHost::OnNewConnection(JingleClient* jingle_client, + scoped_refptr<JingleChannel> channel) { + DCHECK_EQ(jingle_client_.get(), jingle_client); + + // Since the session manager has not started, it is still safe to access + // the client directly. Note that we give the ownership of the channel + // to the client. + client_->set_jingle_channel(channel); +} + +} // namespace remoting diff --git a/remoting/host/simple_host.h b/remoting/host/simple_host.h new file mode 100644 index 0000000..760256a --- /dev/null +++ b/remoting/host/simple_host.h @@ -0,0 +1,131 @@ +// 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. + +#ifndef REMOTING_SIMPLE_HOST_H_ +#define REMOTING_SIMPLE_HOST_H_ + +#include <string> + +#include "base/thread.h" +#include "remoting/host/capturer.h" +#include "remoting/host/client_connection.h" +#include "remoting/host/encoder.h" +#include "remoting/host/event_executor.h" +#include "remoting/host/heartbeat_sender.h" +#include "remoting/host/session_manager.h" +#include "remoting/jingle_glue/jingle_client.h" + +namespace remoting { + +// A class to implement the functionality of a host process. +// +// Here's the work flow of this class: +// 1. We should load the saved GAIA ID token or if this is the first +// time the host process runs we should prompt user for the +// credential. We will use this token or credentials to authenicate +// and register the host. +// +// 2. We listen for incoming connection using libjingle. We will create +// a ClientConnection object that wraps around linjingle for transport. Also +// create a SessionManager with appropriate Encoder and Capturer and +// add the ClientConnection to this SessionManager for transporting the +// screen captures. A EventExecutor is created and registered with the +// ClientConnection to receive mouse / keyboard events from the remote +// client. +// This is also the right time to create multiple threads to host +// the above objects. After we have done all the initialization +// we'll start the SessionManager. We'll then enter the running state +// of the host process. +// +// 3. When the user is disconencted, we will pause the SessionManager +// and try to terminate the threads we have created. This will allow +// all pending tasks to complete. After all of that completed we +// return to the idle state. We then go to step (2) if there a new +// incoming connection. +class SimpleHost : public base::RefCountedThreadSafe<SimpleHost>, + public ClientConnection::EventHandler, + public JingleClient::Callback { + public: + SimpleHost(const std::string& username, const std::string& password, + Capturer* capturer, Encoder* encoder, EventExecutor* executor); + + // Run the host porcess. This method returns only after the message loop + // of the host process exits. + void Run(); + + // This method is called when we need to the host process. + void DestroySession(); + + // This method talks to the cloud to register the host process. If + // successful we will start listening to network requests. + void RegisterHost(); + + // This method is called if a client is connected to this object. + void OnClientConnected(ClientConnection* client); + + // This method is called if a client is disconnected from the host. + void OnClientDisconnected(ClientConnection* client); + + //////////////////////////////////////////////////////////////////////////// + // ClientConnection::EventHandler implementations + virtual void HandleMessages(ClientConnection* client, + ClientMessageList* messages); + virtual void OnConnectionOpened(ClientConnection* client); + virtual void OnConnectionClosed(ClientConnection* client); + virtual void OnConnectionFailed(ClientConnection* client); + + //////////////////////////////////////////////////////////////////////////// + // JingleClient::Callback implementations + virtual void OnStateChange(JingleClient* client, JingleClient::State state); + virtual bool OnAcceptConnection( + JingleClient* jingle, const std::string& jid, + JingleChannel::Callback** channel_callback); + virtual void OnNewConnection( + JingleClient* jingle, + scoped_refptr<JingleChannel> channel); + + private: + // The message loop that this class runs on. + MessageLoop main_loop_; + + // A thread that hosts capture operations. + base::Thread capture_thread_; + + // A thread that hosts encode operations. + base::Thread encode_thread_; + + std::string username_; + std::string password_; + + // Capturer to be used by SessionManager. Once the SessionManager is + // constructed this is set to NULL. + scoped_ptr<Capturer> capturer_; + + // Encoder to be used by the SessionManager. Once the SessionManager is + // constructed this is set to NULL. + scoped_ptr<Encoder> encoder_; + + // EventExecutor executes input events received from the client. + scoped_ptr<EventExecutor> executor_; + + // The libjingle client. This is used to connect to the talk network to + // receive connection requests from chromoting client. + scoped_refptr<JingleClient> jingle_client_; + + // Objects that takes care of sending heartbeats to the chromoting bot. + scoped_refptr<HeartbeatSender> heartbeat_sender_; + + // A ClientConnection manages the connectino to a remote client. + // TODO(hclam): Expand this to a list of clients. + scoped_refptr<ClientConnection> client_; + + // Session manager for the host process. + scoped_refptr<SessionManager> session_; + + DISALLOW_COPY_AND_ASSIGN(SimpleHost); +}; + +} // namespace remoting + +#endif // REMOTING_HOST_SIMPLE_HOST_H_ diff --git a/remoting/host/simple_host_process.cc b/remoting/host/simple_host_process.cc new file mode 100644 index 0000000..e3ed694 --- /dev/null +++ b/remoting/host/simple_host_process.cc @@ -0,0 +1,118 @@ +// 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. +// +// This is an application of a minimal host process in a Chromoting +// system. It serves the purpose of gluing different pieces together +// to make a functional host process for testing. +// +// It peforms the following functionality: +// 1. Connect to the GTalk network and register the machine as a host. +// 2. Accepts connection through libjingle. +// 3. Receive mouse / keyboard events through libjingle. +// 4. Sends screen capture through libjingle. + +#include <iostream> +#include <string> + +#include "build/build_config.h" + +#if defined(OS_POSIX) +#include <termios.h> +#endif // defined (OS_POSIX) + +#include "base/at_exit.h" +#include "remoting/host/capturer_fake.h" +#include "remoting/host/encoder_verbatim.h" +#include "remoting/host/simple_host.h" + +#if defined(OS_WIN) +#include "remoting/host/capturer_gdi.h" +#include "remoting/host/event_executor_win.h" +#elif defined(OS_LINUX) +#include "remoting/host/capturer_linux.h" +#include "remoting/host/event_executor_linux.h" +#elif defined(OS_MAC) +#include "remoting/host/capturer_mac.h" +#include "remoting/host/event_executor_mac.h" +#endif + +void SetConsoleEcho(bool on) { +#if defined(OS_WIN) + HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE); + if ((hIn == INVALID_HANDLE_VALUE) || (hIn == NULL)) + return; + + DWORD mode; + if (!GetConsoleMode(hIn, &mode)) + return; + + if (on) { + mode = mode | ENABLE_ECHO_INPUT; + } else { + mode = mode & ~ENABLE_ECHO_INPUT; + } + + SetConsoleMode(hIn, mode); +#elif defined(OS_POSIX) + struct termios settings; + tcgetattr(STDIN_FILENO, &settings); + if (on) { + settings.c_lflag |= ECHO; + } else { + settings.c_lflag &= ~ECHO; + } + tcsetattr(STDIN_FILENO, TCSANOW, &settings); +#endif // defined(OS_WIN) +} + +int main(int argc, char** argv) { + base::AtExitManager exit_manager; + + // Check the argument to see if we should use a fake capturer and encoder. + bool fake = false; + if (argc > 1 && std::string(argv[1]) == "--fake") { + fake = true; + } + + // Prompt user for username and password. + std::string username; + std::cout << "JID: "; + std::cin >> username; + std::string password; + SetConsoleEcho(false); + std::cout << "Password: "; + std::cin >> password; + SetConsoleEcho(true); + std::cout << std::endl; + + scoped_ptr<remoting::Capturer> capturer; + scoped_ptr<remoting::Encoder> encoder; + scoped_ptr<remoting::EventExecutor> executor; +#if defined(OS_WIN) + capturer.reset(new remoting::CapturerGdi()); + executor.reset(new remoting::EventExecutorWin()); +#elif defined(OS_LINUX) + capturer.reset(new remoting::CapturerLinux()); + executor.reset(new remoting::EventExecutorLinux()); +#elif defined(OS_MAC) + capturer.reset(new remoting::CapturerMac()); + executor.reset(new remoting::EventExecutorMac()); +#endif + encoder.reset(new remoting::EncoderVerbatim()); + + if (fake) { + // Inject a fake capturer. + capturer.reset(new remoting::CapturerFake()); + } + + // Construct a simple host with username and password. + // TODO(hclam): Allow the host to load saved credentials. + scoped_refptr<remoting::SimpleHost> host + = new remoting::SimpleHost(username, password, + capturer.release(), + encoder.release(), + executor.release()); + host->Run(); + return 0; +} |