diff options
author | garykac@google.com <garykac@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-06-07 19:58:23 +0000 |
---|---|---|
committer | garykac@google.com <garykac@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-06-07 19:58:23 +0000 |
commit | cb3b1f93130040a150e7fcc57cd4d5a75569685a (patch) | |
tree | ff93665e3c1478c61663d1107cd42dc25b31448d /remoting | |
parent | b0110e822ac2b2db56d1b1542aad06da573cd544 (diff) | |
download | chromium_src-cb3b1f93130040a150e7fcc57cd4d5a75569685a.zip chromium_src-cb3b1f93130040a150e7fcc57cd4d5a75569685a.tar.gz chromium_src-cb3b1f93130040a150e7fcc57cd4d5a75569685a.tar.bz2 |
Copy the (early prototype of) remoting in Chrome into the public tree.
At the moment, this is a semi-functional demo.
BUG=none
TEST=build/run all unittests on linux
Review URL: http://codereview.chromium.org/2690003
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@49087 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'remoting')
102 files changed, 9366 insertions, 0 deletions
diff --git a/remoting/WATCHLISTS b/remoting/WATCHLISTS new file mode 100644 index 0000000..656fa67 --- /dev/null +++ b/remoting/WATCHLISTS @@ -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. + +# Watchlist Rules +# Refer: http://dev.chromium.org/developers/contributing-code/watchlists + +{ + 'WATCHLIST_DEFINITIONS': { + 'remoting': { + 'filepath': '.+', + }, + }, + + 'WATCHLISTS': { + 'remoting': ['chromoting+reviews@google.com'], + }, +} diff --git a/remoting/base/constants.cc b/remoting/base/constants.cc new file mode 100644 index 0000000..7d42110 --- /dev/null +++ b/remoting/base/constants.cc @@ -0,0 +1,11 @@ +// 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/base/constants.h" + +namespace remoting { + +const std::string kChromotingBotJid("chromoting@bot.talk.google.com"); + +} // namespace remoting diff --git a/remoting/base/constants.h b/remoting/base/constants.h new file mode 100644 index 0000000..190559e --- /dev/null +++ b/remoting/base/constants.h @@ -0,0 +1,16 @@ +// 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_BASE_CONSTANTS_H +#define REMOTING_BASE_CONSTANTS_H + +#include <string> + +namespace remoting { + +extern const std::string kChromotingBotJid; + +} // namespace remoting + +#endif // REMOTING_BASE_CONSTANTS_H diff --git a/remoting/base/mock_objects.h b/remoting/base/mock_objects.h new file mode 100644 index 0000000..a61830e --- /dev/null +++ b/remoting/base/mock_objects.h @@ -0,0 +1,30 @@ +// 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_BASE_MOCK_OBJECTS_H_ +#define REMOTING_BASE_MOCK_OBJECTS_H_ + +#include "remoting/base/protocol_decoder.h" +#include "testing/gmock/include/gmock/gmock.h" + +namespace remoting { + +class MockProtocolDecoder : public ProtocolDecoder { + public: + MockProtocolDecoder() {} + + MOCK_METHOD2(ParseClientMessages, + void(scoped_refptr<media::DataBuffer> data, + ClientMessageList* messages)); + MOCK_METHOD2(ParseHostMessages, + void(scoped_refptr<media::DataBuffer> data, + HostMessageList* messages)); + + private: + DISALLOW_COPY_AND_ASSIGN(MockProtocolDecoder); +}; + +} // namespace remoting + +#endif // REMOTING_BASE_MOCK_OBJECTS_H_ diff --git a/remoting/base/multiple_array_input_stream.cc b/remoting/base/multiple_array_input_stream.cc new file mode 100644 index 0000000..e5b8f94 --- /dev/null +++ b/remoting/base/multiple_array_input_stream.cc @@ -0,0 +1,96 @@ +// 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 <functional> + +#include "base/logging.h" +#include "remoting/base/multiple_array_input_stream.h" + +namespace remoting { + +MultipleArrayInputStream::MultipleArrayInputStream(int count) + : buffer_count_(count), + current_buffer_(0), + current_buffer_offset_(0), + position_(0), + last_returned_size_(0) { + DCHECK_GT(buffer_count_, 0); + buffers_.reset(new const uint8*[buffer_count_]); + buffer_sizes_.reset(new int[buffer_count_]); +} + +MultipleArrayInputStream::~MultipleArrayInputStream() { +} + +bool MultipleArrayInputStream::Next(const void** data, int* size) { + if (current_buffer_ < buffer_count_) { + // Also reply with that is remaining in the current buffer. + last_returned_size_ = + buffer_sizes_[current_buffer_] - current_buffer_offset_; + *data = buffers_[current_buffer_] + current_buffer_offset_; + *size = last_returned_size_; + + // After reading the current buffer then advance to the next buffer. + current_buffer_offset_ = 0; + ++current_buffer_; + position_ += last_returned_size_; + return true; + } + + // We've reached the end of the stream. So reset |last_returned_size_| + // to zero to prevent any backup request. + // This is the same as in ArrayInputStream. + // See google/protobuf/io/zero_copy_stream_impl_lite.cc. + last_returned_size_ = 0; + return false; +} + +void MultipleArrayInputStream::BackUp(int count) { + DCHECK_LE(count, last_returned_size_); + DCHECK_EQ(0, current_buffer_offset_); + DCHECK_GT(current_buffer_, 0); + + // Rewind one buffer. + --current_buffer_; + current_buffer_offset_ = buffer_sizes_[current_buffer_] - count; + position_ -= count; + DCHECK_GE(current_buffer_offset_, 0); + DCHECK_GE(position_, 0); +} + +bool MultipleArrayInputStream::Skip(int count) { + DCHECK_GE(count, 0); + last_returned_size_ = 0; + + while (count && current_buffer_ < buffer_count_) { + int read = std::min( + count, + buffer_sizes_[current_buffer_] - current_buffer_offset_); + + // Advance the current buffer offset and position. + current_buffer_offset_ += read; + position_ += read; + count -= read; + + // If the current buffer is fully read, then advance to the next buffer. + if (current_buffer_offset_ == buffer_sizes_[current_buffer_]) { + ++current_buffer_; + current_buffer_offset_ = 0; + } + } + return count == 0; +} + +int64 MultipleArrayInputStream::ByteCount() const { + return position_; +} + +void MultipleArrayInputStream::SetBuffer( + int n, const uint8* buffer, int size) { + CHECK(n < buffer_count_); + buffers_[n] = buffer; + buffer_sizes_[n] = size; +} + +} // namespace remoting diff --git a/remoting/base/multiple_array_input_stream.h b/remoting/base/multiple_array_input_stream.h new file mode 100644 index 0000000..a1b5f01 --- /dev/null +++ b/remoting/base/multiple_array_input_stream.h @@ -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. + +#ifndef REMOTING_BASE_MULTIPLE_ARRAY_INPUT_STREAM_H_ +#define REMOTING_BASE_MULTIPLE_ARRAY_INPUT_STREAM_H_ + +#include "base/basictypes.h" +#include "base/scoped_ptr.h" +#include "google/protobuf/io/zero_copy_stream.h" + +namespace remoting { + +// A MultipleArrayInputStream provides a ZeroCopyInputStream with multiple +// backing arrays. +class MultipleArrayInputStream : + public google::protobuf::io::ZeroCopyInputStream { + public: + // Construct a MultipleArrayInputStream with |count| backing arrays. + // TODO(hclam): Consider adding block size to see if it has a performance + // gain. + explicit MultipleArrayInputStream(int count); + virtual ~MultipleArrayInputStream(); + + virtual bool Next(const void** data, int* size); + virtual void BackUp(int count); + virtual bool Skip(int count); + virtual int64 ByteCount() const; + + // Set the n-th buffer to be |buffer|. + void SetBuffer(int n, const uint8* buffer, int size); + + private: + scoped_array<const uint8*> buffers_; + scoped_array<int> buffer_sizes_; + + const int buffer_count_; + + int current_buffer_; + int current_buffer_offset_; + int position_; + int last_returned_size_; + + DISALLOW_COPY_AND_ASSIGN(MultipleArrayInputStream); +}; + +} // namespace remoting + +#endif // REMOTING_BASE_MULTIPLE_ARRAY_INPUT_STREAM_H_ diff --git a/remoting/base/multiple_array_input_stream_unittest.cc b/remoting/base/multiple_array_input_stream_unittest.cc new file mode 100644 index 0000000..810596c --- /dev/null +++ b/remoting/base/multiple_array_input_stream_unittest.cc @@ -0,0 +1,94 @@ +// 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 <string> + +#include "remoting/base/multiple_array_input_stream.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace remoting { + +static int ReadFromInput(MultipleArrayInputStream* input, + void* data, int size) { + uint8* out = reinterpret_cast<uint8*>(data); + int out_size = size; + + const void* in; + int in_size = 0; + + while (true) { + if (!input->Next(&in, &in_size)) { + return size - out_size; + } + EXPECT_GT(in_size, -1); + + if (out_size <= in_size) { + memcpy(out, in, out_size); + if (in_size > out_size) { + input->BackUp(in_size - out_size); + } + return size; // Copied all of it. + } + + memcpy(out, in, in_size); + out += in_size; + out_size -= in_size; + } +} + +static void ReadString(MultipleArrayInputStream* input, + const std::string& str) { + scoped_array<char> buffer(new char[str.size() + 1]); + buffer[str.size()] = '\0'; + EXPECT_EQ(ReadFromInput(input, buffer.get(), str.size()), str.size()); + EXPECT_STREQ(str.c_str(), buffer.get()); +} + +// Construct and prepare data in the |output_stream|. +static void PrepareData(scoped_ptr<MultipleArrayInputStream>* stream) { + static const std::string kTestData = + "Hello world!" + "This is testing" + "MultipleArrayInputStream" + "for Chromoting"; + + // Determine how many segments to split kTestData. We split the data in + // 1 character, 2 characters, 1 character, 2 characters ... + int segments = (kTestData.length() / 3) * 2; + int remaining_chars = kTestData.length() % 3; + if (remaining_chars) { + if (remaining_chars == 1) + ++segments; + else + segments += 2; + } + + MultipleArrayInputStream* mstream = new MultipleArrayInputStream(segments); + const char* data = kTestData.c_str(); + for (int i = 0; i < segments; ++i) { + int size = i % 2 == 0 ? 1 : 2; + mstream->SetBuffer(i, reinterpret_cast<const uint8*>(data), size); + data += size; + } + stream->reset(mstream); +} + +TEST(MultipleArrayInputStreamTest, BasicOperations) { + scoped_ptr<MultipleArrayInputStream> stream; + PrepareData(&stream); + + ReadString(stream.get(), "Hello world!"); + ReadString(stream.get(), "This "); + ReadString(stream.get(), "is test"); + EXPECT_TRUE(stream->Skip(3)); + ReadString(stream.get(), "MultipleArrayInput"); + EXPECT_TRUE(stream->Skip(6)); + ReadString(stream.get(), "f"); + ReadString(stream.get(), "o"); + ReadString(stream.get(), "r"); + ReadString(stream.get(), " "); + ReadString(stream.get(), "Chromoting"); +} + +} // namespace remoting diff --git a/remoting/base/protocol/chromotocol.gyp b/remoting/base/protocol/chromotocol.gyp new file mode 100644 index 0000000..bdcb845 --- /dev/null +++ b/remoting/base/protocol/chromotocol.gyp @@ -0,0 +1,79 @@ +# 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. + +{ + 'variables': { + 'chromium_code': 1, + 'out_dir': '<(SHARED_INTERMEDIATE_DIR)/protoc_out/remoting/base/protocol', + }, + 'targets': [ + { + # Protobuf compiler / generate rule for chromoting.proto. + 'target_name': 'chromotocol_proto', + 'type': 'none', + 'sources': [ + 'chromotocol.proto', + ], + 'rules': [ + { + 'rule_name': 'genproto', + 'extension': 'proto', + 'inputs': [ + '<(PRODUCT_DIR)/<(EXECUTABLE_PREFIX)protoc<(EXECUTABLE_SUFFIX)', + ], + 'outputs': [ + '<(PRODUCT_DIR)/pyproto/chromotocol_pb/<(RULE_INPUT_ROOT)_pb2.py', + '<(out_dir)/<(RULE_INPUT_ROOT).pb.cc', + '<(out_dir)/<(RULE_INPUT_ROOT).pb.h', + ], + 'action': [ + '<(PRODUCT_DIR)/<(EXECUTABLE_PREFIX)protoc<(EXECUTABLE_SUFFIX)', + '--proto_path=.', + './<(RULE_INPUT_ROOT)<(RULE_INPUT_EXT)', + '--cpp_out=<(out_dir)', + '--python_out=<(PRODUCT_DIR)/pyproto/chromotocol_pb', + ], + 'message': 'Generating C++ and Python code from <(RULE_INPUT_PATH)', + }, + ], + 'dependencies': [ + '../../../third_party/protobuf2/protobuf.gyp:protoc#host', + ], + # This target exports a hard dependency because it generates header + # files. + 'hard_dependency': 1, + }, + + { + 'target_name': 'chromotocol_proto_lib', + 'type': '<(library)', + 'export_dependent_settings': [ + '../../../third_party/protobuf2/protobuf.gyp:protobuf_lite', + 'chromotocol_proto', + ], + 'dependencies': [ + '../../../third_party/protobuf2/protobuf.gyp:protobuf_lite', + 'chromotocol_proto', + ], + # This target exports a hard dependency because depedents require + # chromotocol_proto to compile. + 'hard_dependency': 1, + 'direct_dependent_settings': { + 'include_dirs': [ + '<(SHARED_INTERMEDIATE_DIR)/protoc_out', + ], + }, + 'sources': [ + '<(out_dir)/chromotocol.pb.cc', + '<(out_dir)/chromotocol.pb.h', + ], + }, + ], +} + +# Local Variables: +# tab-width:2 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=2 shiftwidth=2: diff --git a/remoting/base/protocol/chromotocol.proto b/remoting/base/protocol/chromotocol.proto new file mode 100644 index 0000000..a7907f9 --- /dev/null +++ b/remoting/base/protocol/chromotocol.proto @@ -0,0 +1,153 @@ +// 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. +// +// Protocol for communication between chromoting client and host. + +syntax = "proto2"; + +option optimize_for = LITE_RUNTIME; + +package chromotocol_pb; + +// A message that gets sent to the client after the client is connected to the +// host. It contains information that the client needs to know about the host. +// NEXT ID: 3 +message InitClientMessage { + required int32 width = 1; + required int32 height = 2; +} + +// A message to denote the beginning of an update stream. It will be followed +// by 0 or more PartialUpdateStream messages and then a EndUpdateStream message. +// NEXT ID: 1 +message BeginUpdateStreamMessage { +} + +// A message to denote the end of an update stream. +// NEXT ID: 1 +message EndUpdateStreamMessage { +} + +// Identifies how the image was encoded. +enum UpdateStreamEncoding { + EncodingNone = 0; + EncodingZlib = 1; +} + +// Identifies the pixel format. +// Note that this list should match exactly the same as +// media::VideoFrame::Format in media/base/video_frame.h. +enum PixelFormat { + PixelFormatInvalid = 0; + PixelFormatRgb555 = 1; + PixelFormatRgb565 = 2; + PixelFormatRgb24 = 3; + PixelFormatRgb32 = 4; + PixelFormatRgba = 5; + PixelFormatYv12 = 6; + PixelFormatYv16 = 7; + PixelFormatEmpty = 8; + PixelFormatAscii = 9; +} + +// A message with info about the update stream. +// NEXT ID: 6 +message UpdateStreamPacketHeader { + // X,Y coordinates (in screen pixels) for origin of this update. + required int32 x = 1; + required int32 y = 2; + + // Width, height (in screen pixels) for this update. + required int32 width = 3; + required int32 height = 4; + + // The encoding used for this image update. + optional UpdateStreamEncoding encoding = 5 [default=EncodingNone]; + + // The pixel format of this image. + optional PixelFormat pixel_format = 6 [default=PixelFormatRgb24]; +} + +// A message to denote a partial update stream. +// NEXT ID: 3 +message UpdateStreamPacketMessage { + // TODO(garykac): Make this required and fix unit tests. + optional UpdateStreamPacketHeader header = 2; + optional bytes data = 1; +} + +// Defines the message that is sent from the host to the client. +// Only one of these messages should be present. +// NEXT ID: 5 +message HostMessage { + optional InitClientMessage init_client= 1; + optional BeginUpdateStreamMessage begin_update_stream = 2; + optional EndUpdateStreamMessage end_update_stream = 3; + optional UpdateStreamPacketMessage update_stream_packet = 4; +} + +// Defines a keyboard event. +// NEXT ID: 3 +message KeyEvent { + // The POSIX key code. + required int32 key = 1; + required bool pressed = 2; +} + +// Sets the position of the mouse cursor. +// The coordinate value is between [0 .. 1] which is relative to the +// dimension of the screen area. +// NEXT ID: 3 +message MouseSetPositionEvent { + required float x = 1; + required float y = 2; +} + +// Adjust the position of the mouse cursor by an offset. +// NEXT ID: 3 +message MouseMoveEvent { + required int32 offset_x = 1; + required int32 offset_y = 2; +} + +// Motion of the mouse wheel. +// NEXT ID: 3 +message MouseWheelEvent { + required int32 offset_x = 1; + required int32 offset_y = 2; +} + +// Mouse button is pressed down. +// NEXT ID: 2 +message MouseDownEvent { + enum Button { + LEFT = 0; + MIDDLE = 1; + RIGHT = 2; + } + required Button button = 1; +} + +// Mouse button is released. +// NEXT ID: 2 +message MouseUpEvent { + enum Button { + LEFT = 0; + MIDDLE = 1; + RIGHT = 2; + } + required Button button = 1; +} + +// Defines the message that is sent from the client to the host. +// Only one of these messages should be present. +// NEXT ID: 7 +message ClientMessage { + optional KeyEvent key_event = 1; + optional MouseSetPositionEvent mouse_set_position_event = 2; + optional MouseMoveEvent mouse_move_event = 3; + optional MouseWheelEvent mouse_wheel_event = 4; + optional MouseDownEvent mouse_down_event = 5; + optional MouseUpEvent mouse_up_event = 6; +} diff --git a/remoting/base/protocol_decoder.cc b/remoting/base/protocol_decoder.cc new file mode 100644 index 0000000..1334b20 --- /dev/null +++ b/remoting/base/protocol_decoder.cc @@ -0,0 +1,149 @@ +// 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/base/protocol_decoder.h" + +#include "remoting/base/multiple_array_input_stream.h" +#include "talk/base/byteorder.h" + +namespace remoting { + +ProtocolDecoder::ProtocolDecoder() + : last_read_position_(0), + available_bytes_(0), + next_payload_(0), + next_payload_known_(false) { +} + +void ProtocolDecoder::ParseClientMessages(scoped_refptr<media::DataBuffer> data, + ClientMessageList* messages) { + ParseMessages<chromotocol_pb::ClientMessage>(data, messages); +} + +void ProtocolDecoder::ParseHostMessages(scoped_refptr<media::DataBuffer> data, + HostMessageList* messages) { + ParseMessages<chromotocol_pb::HostMessage>(data, messages); +} + +template <typename T> +void ProtocolDecoder::ParseMessages(scoped_refptr<media::DataBuffer> data, + std::vector<T*>* messages) { + // If this is the first data in the processing queue, then set the + // last read position to 0. + if (data_list_.empty()) + last_read_position_ = 0; + + // First enqueue the data received. + data_list_.push_back(data); + available_bytes_ += data->GetDataSize(); + + // Then try to parse one message until we can't parse anymore. + T* message; + while (ParseOneMessage<T>(&message)) { + messages->push_back(message); + } +} + +template <typename T> +bool ProtocolDecoder::ParseOneMessage(T** message) { + // Determine the payload size. If we already know it, then skip this + // part. + // We have the value set to -1 for checking later. + int next_payload = -1; + if (!next_payload_known_ && GetPayloadSize(&next_payload)) { + DCHECK_NE(-1, next_payload); + next_payload_ = next_payload; + next_payload_known_ = true; + } + + // If the next payload size is still not known or we don't have enough + // data for parsing then exit. + if (!next_payload_known_ || available_bytes_ < next_payload_) + return false; + next_payload_known_ = false; + + // Extract data from |data_list_| used to form a full protocol buffer. + DataList buffers; + std::deque<const uint8*> buffer_pointers; + std::deque<int> buffer_sizes; + while (next_payload_ > 0 && !data_list_.empty()) { + scoped_refptr<media::DataBuffer> buffer = data_list_.front(); + int read_bytes = std::min( + static_cast<int>(buffer->GetDataSize()) - last_read_position_, + next_payload_); + + buffers.push_back(buffer); + buffer_pointers.push_back(buffer->GetData() + last_read_position_); + buffer_sizes.push_back(read_bytes); + + // Adjust counters. + last_read_position_ += read_bytes; + next_payload_ -= read_bytes; + available_bytes_ -= read_bytes; + + // If the front buffer is fully read, remove it from the queue. + if (buffer->GetDataSize() == last_read_position_) { + data_list_.pop_front(); + last_read_position_ = 0; + } + } + DCHECK_EQ(0, next_payload_); + DCHECK_EQ(buffers.size(), buffer_pointers.size()); + DCHECK_EQ(buffers.size(), buffer_sizes.size()); + + // Create a MultipleArrayInputStream for parsing. + MultipleArrayInputStream stream(buffers.size()); + for (size_t i = 0; i < buffers.size(); ++i) { + stream.SetBuffer(i, buffer_pointers[i], buffer_sizes[i]); + } + + // And finally it is parsing. + *message = new T(); + bool ret = (*message)->ParseFromZeroCopyStream(&stream); + if (!ret) + delete *message; + return ret; +} + +bool ProtocolDecoder::GetPayloadSize(int* size) { + // The header has a size of 4 bytes. + const int kHeaderSize = sizeof(int32); + + if (available_bytes_ < kHeaderSize) + return false; + + std::string header; + while (header.length() < kHeaderSize && !data_list_.empty()) { + scoped_refptr<media::DataBuffer> buffer = data_list_.front(); + + // Find out how many bytes we need and how many bytes are available in this + // buffer. + int needed_bytes = kHeaderSize - header.length(); + int available_bytes = buffer->GetDataSize() - last_read_position_; + + // Then append the required bytes into the header and advance the last + // read position. + int read_bytes = std::min(needed_bytes, available_bytes); + header.append( + reinterpret_cast<const char*>(buffer->GetData()) + last_read_position_, + read_bytes); + last_read_position_ += read_bytes; + available_bytes_ -= read_bytes; + + // If the buffer is depleted then remove it from the queue. + if (last_read_position_ == buffer->GetDataSize()) { + last_read_position_ = 0; + data_list_.pop_front(); + } + } + + if (header.length() == kHeaderSize) { + *size = talk_base::GetBE32(header.c_str()); + return true; + } + NOTREACHED() << "Unable to extract payload size"; + return false; +} + +} // namespace remoting diff --git a/remoting/base/protocol_decoder.h b/remoting/base/protocol_decoder.h new file mode 100644 index 0000000..9350e51 --- /dev/null +++ b/remoting/base/protocol_decoder.h @@ -0,0 +1,72 @@ +// 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_BASE_PROTOCOL_DECODER_H_ +#define REMOTING_BASE_PROTOCOL_DECODER_H_ + +#include <deque> +#include <vector> + +#include "base/ref_counted.h" +#include "google/protobuf/message_lite.h" +#include "media/base/data_buffer.h" +#include "remoting/base/protocol/chromotocol.pb.h" + +namespace remoting { + +typedef std::vector<chromotocol_pb::HostMessage*> HostMessageList; +typedef std::vector<chromotocol_pb::ClientMessage*> ClientMessageList; + +// A protocol decoder is used to decode data transmitted in the chromoting +// network. +// TODO(hclam): Defines the interface and implement methods. +class ProtocolDecoder { + public: + ProtocolDecoder(); + + virtual ~ProtocolDecoder() {} + + // Parse data received from network into ClientMessages. Ownership of |data| + // is passed to this object and output is written to |messages|. + virtual void ParseClientMessages(scoped_refptr<media::DataBuffer> data, + ClientMessageList* messages); + + // Parse data received from network into HostMessages. Ownership of |data| + // is passed to this object and output is written to |messages|. + virtual void ParseHostMessages(scoped_refptr<media::DataBuffer> data, + HostMessageList* messages); + + private: + // A private method used to parse data received from network into protocol + // buffers. + template <typename T> + void ParseMessages(scoped_refptr<media::DataBuffer> data, + std::vector<T*>* messages); + + // Parse one message from |data_list_|. Return true if sucessful. + template <typename T> + bool ParseOneMessage(T** messages); + + // A utility method to read payload size of the protocol buffer from the + // data list. Return false if we don't have enough data. + bool GetPayloadSize(int* size); + + typedef std::deque<scoped_refptr<media::DataBuffer> > DataList; + DataList data_list_; + int last_read_position_; + + // Count the number of bytes in |data_list_| not read. + int available_bytes_; + + // Stores the size of the next payload if known. + int next_payload_; + + // True if the size of the next payload is known. After one payload is read, + // this is reset to false. + bool next_payload_known_; +}; + +} // namespace remoting + +#endif // REMOTING_BASE_PROTOCOL_DECODER_H_ diff --git a/remoting/base/protocol_decoder_unittest.cc b/remoting/base/protocol_decoder_unittest.cc new file mode 100644 index 0000000..900f7eb --- /dev/null +++ b/remoting/base/protocol_decoder_unittest.cc @@ -0,0 +1,113 @@ +// 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 <string> + +#include "base/scoped_ptr.h" +#include "media/base/data_buffer.h" +#include "remoting/base/protocol_decoder.h" +#include "remoting/base/protocol_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace remoting { + +static const int kWidth = 640; +static const int kHeight = 480; +static const std::string kTestData = "Chromoting rockz"; + +static void AppendMessage(const chromotocol_pb::HostMessage& msg, + std::string* buffer) { + // Contains one encoded message. + scoped_refptr<media::DataBuffer> encoded_msg; + encoded_msg = SerializeAndFrameMessage(msg); + buffer->append(reinterpret_cast<const char*>(encoded_msg->GetData()), + encoded_msg->GetDataSize()); +} + +// Construct and prepare data in the |output_stream|. +static void PrepareData(uint8** buffer, int* size) { + // Contains all encoded messages. + std::string encoded_data; + + // The first message is InitClient. + chromotocol_pb::HostMessage msg; + msg.mutable_init_client()->set_width(kWidth); + msg.mutable_init_client()->set_height(kHeight); + AppendMessage(msg, &encoded_data); + msg.Clear(); + + // Then append 10 update sequences to the data. + for (int i = 0; i < 10; ++i) { + msg.mutable_begin_update_stream(); + AppendMessage(msg, &encoded_data); + msg.Clear(); + + msg.mutable_update_stream_packet()->set_data(kTestData); + AppendMessage(msg, &encoded_data); + msg.Clear(); + + msg.mutable_end_update_stream(); + AppendMessage(msg, &encoded_data); + msg.Clear(); + } + + *size = encoded_data.length(); + *buffer = new uint8[*size]; + memcpy(*buffer, encoded_data.c_str(), *size); +} + +TEST(ProtocolDecoderTest, BasicOperations) { + // Prepare encoded data for testing. + int size; + uint8* test_data; + PrepareData(&test_data, &size); + scoped_array<uint8> memory_deleter(test_data); + + // Then simulate using ProtocolDecoder to decode variable + // size of encoded data. + // The first thing to do is to generate a variable size of data. This is done + // by iterating the following array for read sizes. + const int kReadSizes[] = {1, 2, 3, 1}; + + ProtocolDecoder decoder; + + // Then feed the protocol decoder using the above generated data and the + // read pattern. + HostMessageList message_list; + for (int i = 0; i < size;) { + // First generate the amount to feed the decoder. + int read = std::min(size - i, kReadSizes[i % arraysize(kReadSizes)]); + + // And then prepare a DataBuffer for feeding it. + scoped_refptr<media::DataBuffer> buffer = new media::DataBuffer(read); + memcpy(buffer->GetWritableData(), test_data + i, read); + buffer->SetDataSize(read); + decoder.ParseHostMessages(buffer, &message_list); + i += read; + } + + // Then verify the decoded messages. + EXPECT_EQ(31u, message_list.size()); + ASSERT_TRUE(message_list.size() > 0); + EXPECT_TRUE(message_list[0]->has_init_client()); + delete message_list[0]; + + for (size_t i = 1; i < message_list.size(); ++i) { + int type = (i - 1) % 3; + if (type == 0) { + // Begin update stream. + EXPECT_TRUE(message_list[i]->has_begin_update_stream()); + } else if (type == 1) { + // Partial update stream. + EXPECT_TRUE(message_list[i]->has_update_stream_packet()); + EXPECT_EQ(kTestData, message_list[i]->update_stream_packet().data()); + } else if (type == 2) { + // End update stream. + EXPECT_TRUE(message_list[i]->has_end_update_stream()); + } + delete message_list[i]; + } +} + +} // namespace remoting diff --git a/remoting/base/protocol_util.cc b/remoting/base/protocol_util.cc new file mode 100644 index 0000000..3ac738c5 --- /dev/null +++ b/remoting/base/protocol_util.cc @@ -0,0 +1,27 @@ +// 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/base/protocol_util.h" + +#include "base/basictypes.h" +#include "base/hash_tables.h" +#include "talk/base/byteorder.h" + +namespace remoting { + +scoped_refptr<media::DataBuffer> SerializeAndFrameMessage( + const google::protobuf::MessageLite& msg) { + + // Create a buffer with 4 extra bytes. This is used as prefix to write an + // int32 of the serialized message size for framing. + const int kExtraBytes = sizeof(int32); + int size = msg.ByteSize() + kExtraBytes; + scoped_refptr<media::DataBuffer> buffer = new media::DataBuffer(size); + talk_base::SetBE32(buffer->GetWritableData(), msg.GetCachedSize()); + msg.SerializeWithCachedSizesToArray(buffer->GetWritableData() + kExtraBytes); + buffer->SetDataSize(size); + return buffer; +} + +} // namespace remoting diff --git a/remoting/base/protocol_util.h b/remoting/base/protocol_util.h new file mode 100644 index 0000000..16781eb --- /dev/null +++ b/remoting/base/protocol_util.h @@ -0,0 +1,24 @@ +// 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_BASE_PROTOCOL_UTIL_H_ +#define REMOTING_BASE_PROTOCOL_UTIL_H_ + +#include "google/protobuf/message_lite.h" +#include "media/base/data_buffer.h" + +// This file defines utility methods used for encoding and decoding the protocol +// used in Chromoting. +namespace remoting { + +// Serialize the Protocol Buffer message and provide sufficient framing for +// sending it over the wire. +// This will provide sufficient prefix and suffix for the receiver side to +// decode the message. +scoped_refptr<media::DataBuffer> SerializeAndFrameMessage( + const google::protobuf::MessageLite& msg); + +} // namespace remoting + +#endif // REMOTING_BASE_PROTOCOL_UTIL_H_ diff --git a/remoting/chromoting.gyp b/remoting/chromoting.gyp new file mode 100644 index 0000000..3136800 --- /dev/null +++ b/remoting/chromoting.gyp @@ -0,0 +1,362 @@ +# 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. + +{ + 'target_defaults': { + 'defines': [ + ], + 'include_dirs': [ + '..', # Root of Chrome checkout + ], + }, + + 'conditions': [ + # Chromoting Client targets + ['OS=="linux" or OS=="mac"', { + 'targets': [ + { + 'target_name': 'chromoting_client_plugin_lib', + 'type': 'static_library', + 'defines': [ + 'HAVE_STDINT_H', # Required by on2_integer.h + ], + 'dependencies': [ + '../third_party/zlib/zlib.gyp:zlib', + ], + 'sources': [ + 'client/plugin/chromoting_main.cc', + 'client/plugin/chromoting_plugin.cc', + 'client/plugin/chromoting_plugin.h', + 'client/plugin/client.cc', + 'client/plugin/client.h', + 'client/plugin/compression.cc', + 'client/plugin/compression.h', + 'client/plugin/decoder.h', + 'client/plugin/host_connection.cc', + 'client/plugin/host_connection.h', + 'client/pepper/pepper_plugin.cc', + 'client/pepper/pepper_plugin.h', + '../media/base/yuv_convert.cc', + '../media/base/yuv_convert.h', + '../media/base/yuv_row.h', + '../media/base/yuv_row_posix.cc', + '../media/base/yuv_row_table.cc', + ], + 'conditions': [ + ['OS=="win"', { + 'sources': [ + '../media/base/yuv_row_win.cc', + ], + }], + ['OS=="linux" and target_arch=="x64" and linux_fpic!=1', { + # Shared libraries need -fPIC on x86-64 + 'cflags': ['-fPIC'], + }], + ['OS=="mac"', { + 'direct_dependent_settings': { + 'libraries': [ + '$(SDKROOT)/usr/lib/libz.dylib', + 'demo/third_party/on2/lib/mac/libon2_codecs.a', + ], + }, + }], + ], # end of 'conditions' + }, # end of target 'chromoting_client_plugin_lib' + + # Client plugin: libchromoting_plugin.so. + { + 'target_name': 'chromoting_client_plugin', + 'type': 'shared_library', + 'product_name': 'chromoting_plugin', + 'dependencies': [ + 'chromoting_client_plugin_lib', + ], + 'sources': [ + # Required here (rather than in lib) so that functions are + # exported properly. + 'client/pepper/pepper_main.cc', + ], + 'conditions': [ + + ['OS=="linux" and target_arch=="x64" and linux_fpic!=1', { + # Shared libraries need -fPIC on x86-64 + 'cflags': [ + '-fPIC' + ], + }], + ], # end of 'conditions' + }, # end of target 'chromoting_client_plugin' + + # Simple webserver for testing chromoting client plugin. + { + 'target_name': 'chromoting_client_test_webserver', + 'type': 'executable', + 'sources': [ + 'tools/client_webserver/main.c', + ], + }, # end of target 'chromoting_client_test_webserver' + + ], # end of Client targets + }], # end of OS conditions for Client targets + + ], # end of 'conditions' + + 'targets': [ + { + 'target_name': 'chromoting_base', + 'type': '<(library)', + 'dependencies': [ + '../gfx/gfx.gyp:*', + '../media/media.gyp:media', + '../third_party/protobuf2/protobuf.gyp:protobuf_lite', + 'base/protocol/chromotocol.gyp:chromotocol_proto_lib', + 'chromoting_jingle_glue', + # TODO(hclam): Enable VP8 in the build. + #'third_party/on2/on2.gyp:vp8', + ], + 'export_dependent_settings': [ + '../third_party/protobuf2/protobuf.gyp:protobuf_lite', + 'base/protocol/chromotocol.gyp:chromotocol_proto_lib', + 'chromoting_jingle_glue', + # TODO(hclam): Enable VP8 in the build. + #'third_party/on2/on2.gyp:vp8', + ], + # This target needs a hard dependency because dependent targets + # depend on chromotocol_proto_lib for headers. + 'hard_dependency': 1, + 'sources': [ + 'base/constants.cc', + 'base/constants.h', + 'base/multiple_array_input_stream.cc', + 'base/multiple_array_input_stream.h', + 'base/protocol_decoder.cc', + 'base/protocol_decoder.h', + 'base/protocol_util.cc', + 'base/protocol_util.h', + ], + }, # end of target 'chromoting_base' + + { + 'target_name': 'chromoting_host', + 'type': '<(library)', + 'dependencies': [ + 'chromoting_base', + 'chromoting_jingle_glue', + ], + 'sources': [ + 'host/capturer.cc', + 'host/capturer.h', + 'host/client_connection.cc', + 'host/client_connection.h', + 'host/differ_block.h', + 'host/differ_block.cc', + 'host/encoder.h', + 'host/encoder_verbatim.cc', + 'host/encoder_verbatim.h', + # TODO(hclam): Enable VP8 in the build. + #'host/encoder_vp8.cc', + #'host/encoder_vp8.h', + 'host/event_executor.h', + 'host/session_manager.cc', + 'host/session_manager.h', + 'host/simple_host.cc', + 'host/simple_host.h', + 'host/heartbeat_sender.cc', + 'host/heartbeat_sender.h', + ], + 'conditions': [ + ['OS=="win"', { + 'sources': [ + 'host/capturer_gdi.cc', + 'host/capturer_gdi.h', + 'host/event_executor_win.cc', + 'host/event_executor_win.h', + ], + }], + ['OS=="linux"', { + 'sources': [ + 'host/capturer_linux.cc', + 'host/capturer_linux.h', + 'host/event_executor_linux.cc', + 'host/event_executor_linux.h', + ], + }], + ['OS=="mac"', { + 'sources': [ + 'host/capturer_mac.cc', + 'host/capturer_mac.h', + 'host/event_executor_mac.cc', + 'host/event_executor_mac.h', + ], + }], + ], + }, # end of target 'chromoting_host' + + { + 'target_name': 'chromoting_client', + 'type': '<(library)', + 'dependencies': [ + 'chromoting_base', + 'chromoting_jingle_glue', + ], + 'sources': [ + 'client/decoder.h', + 'client/decoder_verbatim.cc', + 'client/decoder_verbatim.h', + ], + }, # end of target 'chromoting_client' + + { + 'target_name': 'chromoting_simple_host', + 'type': 'executable', + 'dependencies': [ + 'chromoting_base', + 'chromoting_host', + '../base/base.gyp:base', + '../base/base.gyp:base_i18n', + ], + 'sources': [ + 'host/capturer_fake.cc', + 'host/capturer_fake.h', + 'host/capturer_fake_ascii.cc', + 'host/capturer_fake_ascii.h', + 'host/simple_host_process.cc', + ], + }, # end of target 'chromoting_simple_host' + + { + 'target_name': 'chromoting_simple_client', + 'type': 'executable', + 'dependencies': [ + 'chromoting_base', + 'chromoting_client', + 'chromoting_jingle_glue', + ], + 'sources': [ + 'client/host_connection.cc', + 'client/host_connection.h', + 'client/simple_client.cc', + ], + }, # end of target 'chromoting_simple_client' + + { + 'target_name': 'chromoting_jingle_glue', + 'type': '<(library)', + 'dependencies': [ + '../third_party/libjingle/libjingle.gyp:libjingle', + '../third_party/libjingle/libjingle.gyp:libjingle_p2p', + '../chrome/chrome.gyp:notifier', + ], + 'export_dependent_settings': [ + '../third_party/libjingle/libjingle.gyp:libjingle', + '../third_party/libjingle/libjingle.gyp:libjingle_p2p', + ], + 'sources': [ + 'jingle_glue/iq_request.h', + 'jingle_glue/iq_request.cc', + 'jingle_glue/jingle_channel.h', + 'jingle_glue/jingle_channel.cc', + 'jingle_glue/jingle_client.h', + 'jingle_glue/jingle_client.cc', + 'jingle_glue/jingle_info_task.h', + 'jingle_glue/jingle_info_task.cc', + 'jingle_glue/jingle_thread.h', + 'jingle_glue/jingle_thread.cc', + 'jingle_glue/relay_port_allocator.h', + 'jingle_glue/relay_port_allocator.cc', + ], + }, # end of target 'chromoting_jingle_glue' + + { + 'target_name': 'chromoting_jingle_test_client', + 'type': 'executable', + 'dependencies': [ + 'chromoting_jingle_glue', + '../media/media.gyp:media', + ], + 'sources': [ + 'jingle_glue/jingle_test_client.cc', + ], + }, # end of target 'chromoting_jingle_test_client' + + # Chromoting unit tests + { + 'target_name': 'chromoting_unittests', + 'type': 'executable', + 'dependencies': [ + 'chromoting_base', + 'chromoting_client', + 'chromoting_host', + 'chromoting_jingle_glue', + '../base/base.gyp:base', + '../base/base.gyp:base_i18n', + '../gfx/gfx.gyp:*', + '../testing/gmock.gyp:gmock', + '../testing/gtest.gyp:gtest', + ], + 'include_dirs': [ + '../testing/gmock/include', + ], + 'sources': [ + 'host/client_connection_unittest.cc', + 'base/mock_objects.h', + 'base/multiple_array_input_stream_unittest.cc', + 'base/protocol_decoder_unittest.cc', + 'client/decoder_verbatim_unittest.cc', + 'host/differ_block_unittest.cc', + 'host/mock_objects.h', + 'host/session_manager_unittest.cc', + # TODO(hclam): Enable VP8 in the build. + #'host/encoder_vp8_unittest.cc', + 'jingle_glue/jingle_thread_unittest.cc', + 'jingle_glue/jingle_channel_unittest.cc', + 'jingle_glue/iq_request_unittest.cc', + 'jingle_glue/mock_objects.h', + 'run_all_unittests.cc', + ], + 'conditions': [ + ['OS=="win"', { + 'sources': [ + 'host/capturer_gdi_unittest.cc', + ], + }], + ['OS=="linux"', { + 'dependencies': [ + # Needed for the following #include chain: + # base/run_all_unittests.cc + # ../base/test_suite.h + # gtk/gtk.h + '../build/linux/system.gyp:gtk', + ], + 'sources': [ + 'host/capturer_linux_unittest.cc', + ], + }], + ['OS=="mac"', { + 'sources': [ + 'host/capturer_mac_unittest.cc', + ], + }], + ['OS=="linux" or OS=="mac"', { + 'dependencies': [ + 'chromoting_client_plugin_lib', + ], + 'sources': [ + 'client/plugin/chromoting_plugin_unittest.cc', + 'client/pepper/pepper_main.cc', + 'client/pepper/fake_browser.cc', + 'client/pepper/fake_browser.h', + ], + }], + ], # end of 'conditions' + }, # end of target 'chromoting_unittests' + + ], # end of targets +} + +# Local Variables: +# tab-width:2 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=2 shiftwidth=2: diff --git a/remoting/client/decoder.h b/remoting/client/decoder.h new file mode 100644 index 0000000..d39be07 --- /dev/null +++ b/remoting/client/decoder.h @@ -0,0 +1,92 @@ +// 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_CLIENT_DECODER_H_ +#define REMOTING_CLIENT_DECODER_H_ + +#include <vector> + +#include "base/callback.h" +#include "base/scoped_ptr.h" +#include "gfx/rect.h" +#include "media/base/video_frame.h" +#include "remoting/base/protocol/chromotocol.pb.h" + +namespace remoting { + +// Defines the behavior of a decoder for decoding images received from the +// host. +// +// Sequence of actions with a decoder is as follows: +// +// 1. BeginDecode(VideoFrame) +// 2. PartialDecode(HostMessage) +// ... +// 3. EndDecode() +// +// The decoder will reply with: +// 1. PartialDecodeDone(VideoFrame, UpdatedRects) +// ... +// 2. DecodeDone(VideoFrame) +// +// The format of VideoFrame is a contract between the object that creates the +// decoder (most likely the renderer) and the decoder. +class Decoder { + public: + typedef std::vector<gfx::Rect> UpdatedRects; + typedef Callback2<scoped_refptr<media::VideoFrame>, UpdatedRects>::Type + PartialDecodeDoneCallback; + typedef Callback1<scoped_refptr<media::VideoFrame> >::Type + DecodeDoneCallback; + + Decoder(PartialDecodeDoneCallback* partial_decode_done_callback, + DecodeDoneCallback* decode_done_callback) + : partial_decode_done_(partial_decode_done_callback), + decode_done_(decode_done_callback) { + } + + virtual ~Decoder() { + } + + // Tell the decoder to use |frame| as a target to write the decoded image + // for the coming update stream. + // Return true if the decoder can writes output to |frame| and accept + // the codec format. + // TODO(hclam): Provide more information when calling this function. + virtual bool BeginDecode(scoped_refptr<media::VideoFrame> frame) = 0; + + // Give a HostMessage that contains the update stream packet that contains + // the encoded data to the decoder. + // The decoder will own |message| and is responsible for deleting it. + // If the decoder has written something into |frame|, + // |partial_decode_done_| is called with |frame| and updated regions. + // Return true if the decoder can accept |message| and decode it. + virtual bool PartialDecode(chromotocol_pb::HostMessage* message) = 0; + + // Notify the decoder that we have received the last update stream packet. + // If the decoding of the update stream has completed |decode_done_| is + // called with |frame|. + // If the update stream is not received fully and this method is called the + // decoder should also call |decode_done_| as soon as possible. + virtual void EndDecode() = 0; + + protected: + PartialDecodeDoneCallback* partial_decode_done() { + return partial_decode_done_.get(); + } + + DecodeDoneCallback* decode_done() { + return decode_done_.get(); + } + + private: + scoped_ptr<PartialDecodeDoneCallback> partial_decode_done_; + scoped_ptr<DecodeDoneCallback> decode_done_; + + DISALLOW_COPY_AND_ASSIGN(Decoder); +}; + +} // namespace remoting + +#endif // REMOTING_CLIENT_DECODER_H_ diff --git a/remoting/client/decoder_verbatim.cc b/remoting/client/decoder_verbatim.cc new file mode 100644 index 0000000..7cbf428 --- /dev/null +++ b/remoting/client/decoder_verbatim.cc @@ -0,0 +1,67 @@ +// 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/client/decoder_verbatim.h" + +namespace remoting { + +bool DecoderVerbatim::BeginDecode(scoped_refptr<media::VideoFrame> frame) { + // TODO(hclam): Check if we can accept the codec. + frame_ = frame; + return true; +} + +bool DecoderVerbatim::PartialDecode(chromotocol_pb::HostMessage* message) { + scoped_ptr<chromotocol_pb::HostMessage> msg_deleter(message); + + // TODO(hclam): Support YUV. + if (static_cast<int>(message->update_stream_packet().header().pixel_format()) + != static_cast<int>(frame_->format())) { + return false; + } + int width = message->update_stream_packet().header().width(); + int height = message->update_stream_packet().header().height(); + int x = message->update_stream_packet().header().x(); + int y = message->update_stream_packet().header().y(); + chromotocol_pb::PixelFormat pixel_format = + message->update_stream_packet().header().pixel_format(); + int bytes_per_pixel = 0; + + // TODO(hclam): Extract the following to an util function. + 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"; + } + + // Copy the data line by line. + const int src_stride = bytes_per_pixel * width; + const char* src = message->update_stream_packet().data().c_str(); + const int dest_stride = frame_->stride(media::VideoFrame::kRGBPlane); + uint8* dest = frame_->data(media::VideoFrame::kRGBPlane) + + dest_stride * y + bytes_per_pixel * x; + for (int i = 0; i < height; ++i) { + memcpy(dest, src, src_stride); + dest += dest_stride; + src += src_stride; + } + + UpdatedRects rects; + rects.push_back(gfx::Rect(x, y, width, height)); + partial_decode_done()->Run(frame_, rects); + return true; +} + +void DecoderVerbatim::EndDecode() { + decode_done()->Run(frame_); + frame_ = NULL; +} + +} // namespace remoting diff --git a/remoting/client/decoder_verbatim.h b/remoting/client/decoder_verbatim.h new file mode 100644 index 0000000..94d2f81 --- /dev/null +++ b/remoting/client/decoder_verbatim.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_CLIENT_DECODER_VERBATIM_H_ +#define REMOTING_CLIENT_DECODER_VERBATIM_H_ + +#include "remoting/client/decoder.h" + +namespace remoting { + +class DecoderVerbatim : public Decoder { + public: + DecoderVerbatim(PartialDecodeDoneCallback* partial_decode_done_callback, + DecodeDoneCallback* decode_done_callback) + : Decoder(partial_decode_done_callback, decode_done_callback) { + } + + // Decoder implementations. + virtual bool BeginDecode(scoped_refptr<media::VideoFrame> frame); + virtual bool PartialDecode(chromotocol_pb::HostMessage* message); + virtual void EndDecode(); + + private: + // The video frame to write to. + scoped_refptr<media::VideoFrame> frame_; + + DISALLOW_COPY_AND_ASSIGN(DecoderVerbatim); +}; + +} // namespace remoting + +#endif // REMOTING_CLIENT_DECODER_VERBATIM_H_ diff --git a/remoting/client/decoder_verbatim_unittest.cc b/remoting/client/decoder_verbatim_unittest.cc new file mode 100644 index 0000000..b9c1d79 --- /dev/null +++ b/remoting/client/decoder_verbatim_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/gtest/include/gtest/gtest.h" + +namespace remoting { + +// TODO(hclam): Implement unit tests. +TEST(DecoderVerbatimTest, Simple) { +} + +} // namespace remoting diff --git a/remoting/client/host_connection.cc b/remoting/client/host_connection.cc new file mode 100644 index 0000000..f858af6 --- /dev/null +++ b/remoting/client/host_connection.cc @@ -0,0 +1,78 @@ +// 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/client/host_connection.h" + +namespace remoting { + +HostConnection::HostConnection(ProtocolDecoder* decoder, + EventHandler* handler) + : decoder_(decoder), handler_(handler) { +} + +HostConnection::~HostConnection() { + Disconnect(); +} + +void HostConnection::Connect(const std::string& username, + const std::string& password, + const std::string& host_jid) { + jingle_client_ = new JingleClient(); + jingle_client_->Init(username, password, this); + jingle_channel_ = jingle_client_->Connect(host_jid, this); +} + +void HostConnection::Disconnect() { + if (jingle_channel_.get()) + jingle_channel_->Close(); + + if (jingle_client_.get()) + jingle_client_->Close(); +} + +void HostConnection::OnStateChange(JingleChannel* channel, + JingleChannel::State state) { + DCHECK(handler_); + if (state == JingleChannel::FAILED) + handler_->OnConnectionFailed(this); + else if (state == JingleChannel::CLOSED) + handler_->OnConnectionClosed(this); + else if (state == JingleChannel::OPEN) + handler_->OnConnectionOpened(this); +} + +void HostConnection::OnPacketReceived(JingleChannel* channel, + scoped_refptr<media::DataBuffer> buffer) { + HostMessageList list; + decoder_->ParseHostMessages(buffer, &list); + DCHECK(handler_); + handler_->HandleMessages(this, &list); +} + +// JingleClient::Callback interface. +void HostConnection::OnStateChange(JingleClient* client, + JingleClient::State state) { + DCHECK(client); + DCHECK(handler_); + if (state == JingleClient::CONNECTED) { + LOG(INFO) << "Connected as: " << client->GetFullJid(); + } else if (state == JingleClient::CLOSED) { + LOG(INFO) << "Connection closed."; + handler_->OnConnectionClosed(this); + } +} + +bool HostConnection::OnAcceptConnection(JingleClient* client, + const std::string& jid, + JingleChannel::Callback** callback) { + // Client rejects all connection. + return false; +} + +void HostConnection::OnNewConnection(JingleClient* client, + scoped_refptr<JingleChannel> channel) { + NOTREACHED() << "SimpleClient can't accept connection."; +} + +} // namespace remoting diff --git a/remoting/client/host_connection.h b/remoting/client/host_connection.h new file mode 100644 index 0000000..af1fda7 --- /dev/null +++ b/remoting/client/host_connection.h @@ -0,0 +1,77 @@ +// 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_CLIENT_HOST_CONNECTION_H_ +#define REMOTING_CLIENT_HOST_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" +#include "remoting/jingle_glue/jingle_client.h" + +namespace remoting { + +class HostConnection : public JingleChannel::Callback, + public JingleClient::Callback { + public: + class EventHandler { + public: + virtual ~EventHandler() {} + + // Handles an event received by the HostConnection. Receiver will own the + // HostMessages in HostMessageList 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(HostConnection* conn, + HostMessageList* messages) = 0; + + // Called when the network connection is opened. + virtual void OnConnectionOpened(HostConnection* conn) = 0; + + // Called when the network connection is closed. + virtual void OnConnectionClosed(HostConnection* conn) = 0; + + // Called when the network connection has failed. + virtual void OnConnectionFailed(HostConnection* conn) = 0; + }; + + // Constructs a HostConnection object. + HostConnection(ProtocolDecoder* decoder, EventHandler* handler); + + virtual ~HostConnection(); + + void Connect(const std::string& username, const std::string& password, + const std::string& host_jid); + void Disconnect(); + + // JingleChannel::Callback interface. + void OnStateChange(JingleChannel* channel, JingleChannel::State state); + void OnPacketReceived(JingleChannel* channel, + scoped_refptr<media::DataBuffer> buffer); + + // JingleClient::Callback interface. + void OnStateChange(JingleClient* client, JingleClient::State state); + bool OnAcceptConnection(JingleClient* client, const std::string& jid, + JingleChannel::Callback** callback); + void OnNewConnection(JingleClient* client, + scoped_refptr<JingleChannel> channel); + + private: + scoped_refptr<JingleClient> jingle_client_; + scoped_refptr<JingleChannel> jingle_channel_; + scoped_ptr<ProtocolDecoder> decoder_; + EventHandler* handler_; + + DISALLOW_COPY_AND_ASSIGN(HostConnection); +}; + +} // namespace remoting + +#endif // REMOTING_CLIENT_HOST_CONNECTION_H_ diff --git a/remoting/client/pepper/fake_browser.cc b/remoting/client/pepper/fake_browser.cc new file mode 100644 index 0000000..a24668c --- /dev/null +++ b/remoting/client/pepper/fake_browser.cc @@ -0,0 +1,157 @@ +// 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 <stdlib.h> + +#include "remoting/client/pepper/fake_browser.h" + +// Constant value for browser window. +static const int kWindowWidth = 100; +static const int kWindowHeight = 100; + +// ---------------------------------------------------------------------------- +// Browser callback routines +// These are simple implementations of the routines that the browser provides +// to the plugin as callbacks. +// ---------------------------------------------------------------------------- + +// Handler for getvalue. +// Must be of type NPN_GetValueProcPtr. +NPError Browser_GetValue(NPP instance, NPNVariable variable, void *ret_value) { + if (variable == NPNVPepperExtensions) { + NPNExtensions** ret = static_cast<NPNExtensions**>(ret_value); + *ret = Singleton<FakeBrowser>()->GetExtensions(); + return NPERR_NO_ERROR; + } + return NPERR_GENERIC_ERROR; +} + +// Extension handler for acquireDevice. +// Must be of type NPAcquireDevicePtr. +NPDevice* Extension_AcquireDevice(NPP instance, NPDeviceID device) { + if (device == NPPepper2DDevice) { + return Singleton<FakeBrowser>()->GetDevice2d(); + } + // TODO(garykac): Add support for NPPepper3DDevice. + return NULL; +} + +// Initialize 2D device context. +NPError Device_InitializeContext2D(NPP instance, + NPDeviceContext2D* context, + int extra_bytes) { + FakeBrowser* browser = Singleton<FakeBrowser>::get(); + NPDeviceContext2D* context2d = static_cast<NPDeviceContext2D*>(context); + int width, height; + browser->GetWindowInfo(&width, &height); + int stride = (width * ARGB_PIXEL_SIZE) + extra_bytes; + context2d->region = browser->AllocPixelBuffer(stride); + context2d->stride = stride; + + return NPERR_NO_ERROR; +} + +// Device handler for initializeContext +// This initializes a 2D context where the stride == width. +// Must be of type NPDeviceInitializeContextPtr. +NPError Device_InitializeContext2D_NoExtraBytes(NPP instance, + const NPDeviceConfig* config, + NPDeviceContext* context) { + return Device_InitializeContext2D(instance, + static_cast<NPDeviceContext2D*>(context), + 0); +} + +// Device handler for initializeContext +// This initializes a 2D context where the stride > width. +// Must be of type NPDeviceInitializeContextPtr. +NPError Device_InitializeContext2D_ExtraBytes(NPP instance, + const NPDeviceConfig* config, + NPDeviceContext* context) { + return Device_InitializeContext2D(instance, + static_cast<NPDeviceContext2D*>(context), + 8 /* extra_bytes */); +} + +// Device handler for flushContext +// Must be of type NPDeviceFlushContextPtr. +NPError Device_FlushContext(NPP instance, NPDeviceContext* context, + NPDeviceFlushContextCallbackPtr callback, + void* userData) { + return NPERR_NO_ERROR; +} + +// ---------------------------------------------------------------------------- +// FakeBrowserFuncs +// Singleton class for creating/managing the NPNetscapeFuncs struct that we +// need to provide to the Pepper plugin. +// ---------------------------------------------------------------------------- + +FakeBrowser::FakeBrowser() { + // Setup fake versions of the browser funcs needed by the unit tests. + // There are dozens of browser funcs that can be set up, but we only worry + // about the ones needed for our unittests. + browser_funcs_.reset(new NPNetscapeFuncs()); + browser_funcs_->getvalue = &Browser_GetValue; + + // Setup fake extension funcs structure. + extensions_.reset(new NPNExtensions()); + extensions_->acquireDevice = &Extension_AcquireDevice; + + // Setup fake device funcs structure. + device2d_.reset(new NPDevice()); + device2d_->initializeContext = &Device_InitializeContext2D_NoExtraBytes; + device2d_->flushContext = &Device_FlushContext; + + // Fake browser window. + window_.reset(new NPWindow()); + window_->x = 0; + window_->y = 0; + window_->width = kWindowWidth; + window_->height = kWindowHeight; + + width_ = kWindowWidth; + height_ = kWindowHeight; + + stride_ = 0; + pixel_buffer_.reset(); +} + +FakeBrowser::~FakeBrowser() { + FreePixelBuffer(); +} + +// Normally in our tests, the stride (ie, the number of bytes between the +// start of a row and the start of the next row) is equal to the number of +// bytes used to store the pixels for the row. +// Passing true to this routine sets things up so that there are a few extra +// padding bytes to the end of each row so that the stride is not the same +// as the row width. +void FakeBrowser::ForceStrideInDeviceContext(bool extra_bytes) { + if (extra_bytes) { + device2d_->initializeContext = &Device_InitializeContext2D_ExtraBytes; + } else { + device2d_->initializeContext = &Device_InitializeContext2D_NoExtraBytes; + } +} + +// Allocate a pixel buffer for the plugin to use. +// The height and width of the buffer come from the window size. +// The stride value is used to force each row to be |stride| bytes in size. +// This is typically done to add extra padding bytes to the end of each row. +uint32* FakeBrowser::AllocPixelBuffer(int stride) { + // Don't allow the stride to be less than the window width. + if (stride < width_) { + stride = width_; + } + stride_ = stride; + pixel_buffer_.reset(new uint32[height_ * stride]); + + return pixel_buffer_.get(); +} + +void FakeBrowser::FreePixelBuffer() { + stride_ = 0; + pixel_buffer_.reset(); +} diff --git a/remoting/client/pepper/fake_browser.h b/remoting/client/pepper/fake_browser.h new file mode 100644 index 0000000..b9f2e15 --- /dev/null +++ b/remoting/client/pepper/fake_browser.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_CLIENT_PEPPER_FAKE_BROWSER_H_ +#define REMOTING_CLIENT_PEPPER_FAKE_BROWSER_H_ + +#include "base/scoped_ptr.h" +#include "base/singleton.h" +#include "third_party/npapi/bindings/nphostapi.h" +#include "third_party/npapi/bindings/npapi_extensions.h" + +// Each ARGB pixel is stored in a 4-byte uint32. +#define ARGB_PIXEL_SIZE 4 + +class FakeBrowser { + public: + NPNetscapeFuncs* GetBrowserFuncs() { return browser_funcs_.get(); } + NPNExtensions* GetExtensions() { return extensions_.get(); } + + NPDevice* GetDevice2d() { return device2d_.get(); } + void ForceStrideInDeviceContext(bool forceStride); + + NPWindow* GetWindow() { return window_.get(); } + void GetWindowInfo(int* width, int* height) { + *width = width_; + *height = height_; + } + + uint32* AllocPixelBuffer(int stride); + void FreePixelBuffer(); + + uint32* GetPixelBuffer() { return pixel_buffer_.get(); } + int GetPixelBufferStride() { return stride_; } + + private: + // Singleton private bits. + friend struct DefaultSingletonTraits<FakeBrowser>; + FakeBrowser(); + virtual ~FakeBrowser(); + + // Browser callback functions. + scoped_ptr<NPNetscapeFuncs> browser_funcs_; + + // Browser extension callbacks. + scoped_ptr<NPNExtensions> extensions_; + + // The rendering device (provided by the browser to the plugin). + scoped_ptr<NPDevice> device2d_; + + // Window (provided by the browser to the plugin). + scoped_ptr<NPWindow> window_; + + // Pixel buffer to store the device2d_ (and window_) pixels. + scoped_array<uint32> pixel_buffer_; + int width_, height_, stride_; + + DISALLOW_COPY_AND_ASSIGN(FakeBrowser); +}; + +#endif // REMOTING_CLIENT_PEPPER_FAKE_BROWSER_H_ diff --git a/remoting/client/pepper/pepper_main.cc b/remoting/client/pepper/pepper_main.cc new file mode 100644 index 0000000..013c90a --- /dev/null +++ b/remoting/client/pepper/pepper_main.cc @@ -0,0 +1,373 @@ +// 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/client/pepper/pepper_plugin.h" +#include "third_party/npapi/bindings/npapi.h" +#include "third_party/npapi/bindings/nphostapi.h" + +#if __GNUC__ >= 4 +# define EXPORT __attribute__ ((visibility("default"))) +# define PRIVATE __attribute__ ((visibility("hidden"))) +#else +# define EXPORT +# define PRIVATE +#endif + +// +// External Plugin Implementation +// + +// Plugin info. +// These routines are defined externally and provide the code that is specific +// to this particular plugin. + +// Initialize general plugin information. +extern void InitializePluginInfo(pepper::PepperPlugin::Info* plugin_info); + +// Routine to create the PepperPlugin subclass that implements all of the +// plugin-specific functionality. +extern pepper::PepperPlugin* CreatePlugin(NPNetscapeFuncs* browser_funcs, + NPP instance); + +namespace pepper { + +// +// Globals +// + +// Pointer to struct containing all the callbacks provided by the browser +// to the plugin. +NPNetscapeFuncs* g_browser_funcs = NULL; + +// General information (name/description) about this plugin. +PepperPlugin::Info g_plugin_info = { false, NULL, NULL, NULL }; + + +// +// Internal setup routines +// + +PRIVATE void Initialize(NPNetscapeFuncs* browser_funcs) { + g_browser_funcs = browser_funcs; + if (!g_plugin_info.initialized) { + InitializePluginInfo(&g_plugin_info); + } +} + +// Populate the NPPluginFuncs struct so that the browser knows how to find +// each entry point for the plugin. +PRIVATE void SetupEntryPoints(NPPluginFuncs* plugin_funcs) { + plugin_funcs->version = ((NP_VERSION_MAJOR << 8) + NP_VERSION_MINOR); + plugin_funcs->size = sizeof(NPPluginFuncs); + plugin_funcs->newp = NPP_New; + plugin_funcs->destroy = NPP_Destroy; + plugin_funcs->setwindow = NPP_SetWindow; + plugin_funcs->newstream = NPP_NewStream; + plugin_funcs->destroystream = NPP_DestroyStream; + plugin_funcs->asfile = NPP_StreamAsFile; + plugin_funcs->writeready = NPP_WriteReady; + plugin_funcs->write = NPP_Write; + plugin_funcs->print = NPP_Print; + plugin_funcs->event = NPP_HandleEvent; + plugin_funcs->urlnotify = NPP_URLNotify; + plugin_funcs->javaClass = NULL; + plugin_funcs->getvalue = NPP_GetValue; + plugin_funcs->setvalue = NPP_SetValue; +} + +// Get the PepperPlugin from the private data storage in the instance. +PRIVATE PepperPlugin* GetPlugin(NPP instance) { + return static_cast<PepperPlugin*>(instance->pdata); +} + +} // namespace pepper + + +// +// Exported interfaces +// Routines to initialize/shutdown the plugin. +// + +extern "C" { + +#if defined(OS_POSIX) && !defined(OS_MACOSX) + +// Get the MIME-type associated with this plugin. +// Linux-only. Mac & Windows use a different mechanism for associating a +// MIME-type with the plugin. +// Note that this is called before NPP_Initialize(). +EXPORT const char* API_CALL NP_GetMIMEDescription() { + if (!pepper::g_plugin_info.initialized) { + InitializePluginInfo(&pepper::g_plugin_info); + } + return pepper::g_plugin_info.mime_description; +} + +// Old version of NPP_GetValue, required for Linux. +// Simply redirects to the NPP_GetValue. +EXPORT NPError API_CALL NP_GetValue(NPP instance, + NPPVariable variable, + void* value) { + return NPP_GetValue(instance, variable, value); +} + +// NP_Initialize for Linux. +// This is the equivalent of NP_Initialize and NP_GetEntryPoints for Mac/Win. +EXPORT NPError API_CALL NP_Initialize(NPNetscapeFuncs* browser_funcs, + NPPluginFuncs* plugin_funcs) { + pepper::Initialize(browser_funcs); + pepper::SetupEntryPoints(plugin_funcs); + return NPERR_NO_ERROR; +} + +#else + +// NP_Initialize for Mac/Windows. +EXPORT NPError API_CALL NP_Initialize(NPNetscapeFuncs* browser_funcs) { + pepper::Initialize(browser_funcs); + return NPERR_NO_ERROR; +} + +// NP_GetEntryPoints for Mac/Windows. +EXPORT NPError API_CALL NP_GetEntryPoints(NPPluginFuncs* plugin_funcs) { + pepper::SetupEntryPoints(plugin_funcs); + return NPERR_NO_ERROR; +} + +#endif // defined(OS_POSIX) && !defined(OS_MACOSX) + +EXPORT NPError API_CALL NP_Shutdown() { + pepper::g_browser_funcs = NULL; + return NPERR_NO_ERROR; +} + +} // extern "C" + + +// +// Plugin Entrypoints +// Entry points that the plugin makes available to the browser. +// + +EXPORT NPError NPP_New(NPMIMEType pluginType, + NPP instance, + uint16 mode, + int16 argc, + char* argn[], + char* argv[], + NPSavedData* saved) { + if (!instance) { + return NPERR_INVALID_INSTANCE_ERROR; + } + + pepper::PepperPlugin* plugin + = CreatePlugin(pepper::g_browser_funcs, instance); + NPError result = plugin->New(pluginType, argc, argn, argv); + if (result != NPERR_NO_ERROR) { + delete plugin; + return result; + } + + instance->pdata = plugin; + return NPERR_NO_ERROR; +} + +EXPORT NPError NPP_Destroy(NPP instance, NPSavedData** save) { + if (!instance) { + return NPERR_INVALID_INSTANCE_ERROR; + } + + NPError result = NPERR_NO_ERROR; + pepper::PepperPlugin* plugin = pepper::GetPlugin(instance); + if (plugin) { + result = plugin->Destroy(save); + if (result != NPERR_NO_ERROR) { + return result; + } + delete plugin; + instance->pdata = NULL; + } + return result; +} + +EXPORT NPError NPP_SetWindow(NPP instance, NPWindow* window) { + if (!instance) { + return NPERR_INVALID_INSTANCE_ERROR; + } + + pepper::PepperPlugin* plugin = pepper::GetPlugin(instance); + if (plugin) { + return plugin->SetWindow(window); + } + + return NPERR_GENERIC_ERROR; +} + +EXPORT NPError NPP_NewStream(NPP instance, + NPMIMEType type, + NPStream* stream, + NPBool seekable, + uint16* stype) { + if (!instance) { + return NPERR_INVALID_INSTANCE_ERROR; + } + + pepper::PepperPlugin* plugin = pepper::GetPlugin(instance); + if (plugin) { + return plugin->NewStream(type, stream, seekable, stype); + } + + return NPERR_GENERIC_ERROR; +} + +EXPORT NPError NPP_DestroyStream(NPP instance, + NPStream* stream, + NPReason reason) { + if (!instance) { + return NPERR_INVALID_INSTANCE_ERROR; + } + + pepper::PepperPlugin* plugin = pepper::GetPlugin(instance); + if (plugin) { + return plugin->DestroyStream(stream, reason); + } + + return NPERR_GENERIC_ERROR; +} + +EXPORT void NPP_StreamAsFile(NPP instance, + NPStream* stream, + const char* fname) { + if (!instance) { + return; + } + + pepper::PepperPlugin* plugin = pepper::GetPlugin(instance); + if (plugin) { + plugin->StreamAsFile(stream, fname); + } +} + +EXPORT int32 NPP_WriteReady(NPP instance, + NPStream* stream) { + if (!instance) { + return 0; + } + + pepper::PepperPlugin* plugin = pepper::GetPlugin(instance); + if (plugin) { + return plugin->WriteReady(stream); + } + + return 0; +} + +EXPORT int32 NPP_Write(NPP instance, + NPStream* stream, + int32 offset, + int32 len, + void* buffer) { + if (!instance) { + return 0; + } + + pepper::PepperPlugin* plugin = pepper::GetPlugin(instance); + if (plugin) { + return plugin->Write(stream, offset, len, buffer); + } + + return 0; +} + +EXPORT void NPP_Print(NPP instance, + NPPrint* platformPrint) { + if (!instance) { + return; + } + + pepper::PepperPlugin* plugin = pepper::GetPlugin(instance); + if (plugin) { + plugin->Print(platformPrint); + } +} + +EXPORT int16 NPP_HandleEvent(NPP instance, + void* event) { + if (!instance) { + return false; + } + + pepper::PepperPlugin* plugin = pepper::GetPlugin(instance); + if (plugin) { + return plugin->HandleEvent(event); + } + + return false; +} + +EXPORT void NPP_URLNotify(NPP instance, + const char* url, + NPReason reason, + void* notifyData) { + if (!instance) { + return; + } + + pepper::PepperPlugin* plugin = pepper::GetPlugin(instance); + if (plugin) { + plugin->URLNotify(url, reason, notifyData); + } +} + +EXPORT NPError NPP_GetValue(NPP instance, + NPPVariable variable, + void* value) { +#if defined(OS_POSIX) && !defined(OS_MACOSX) + // Note that it is valid to call this routine before the plugin instance + // has been created. + // For example, the browser requests the name/description when plugin + // is loaded or when about:plugins is opened (whichever comes first). + // Thus, we can't check for a valid instance instance here and bail if + // it's not setup (like we do for the other routines). + + // If the name/description is being requested, then get that directly. + if (variable == NPPVpluginNameString) { + *((const char**)value) = pepper::g_plugin_info.plugin_name; + return NPERR_NO_ERROR; + } + if (variable == NPPVpluginDescriptionString) { + *((const char**)value) = pepper::g_plugin_info.plugin_description; + return NPERR_NO_ERROR; + } + if (variable == NPPVpluginNeedsXEmbed) { + *(static_cast<NPBool*>(value)) = true; + return NPERR_NO_ERROR; + } +#endif // defined(OS_POSIX) && !defined(OS_MACOSX) + + if (instance) { + // If we have an instance, then let the plugin handle the call. + pepper::PepperPlugin* plugin = pepper::GetPlugin(instance); + if (plugin) { + return plugin->GetValue(variable, value); + } + } + + return NPERR_GENERIC_ERROR; +} + +EXPORT NPError NPP_SetValue(NPP instance, + NPNVariable variable, + void* value) { + if (!instance) { + return NPERR_INVALID_INSTANCE_ERROR; + } + + pepper::PepperPlugin* plugin = pepper::GetPlugin(instance); + if (plugin) { + return plugin->SetValue(variable, value); + } + + return NPERR_GENERIC_ERROR; +} diff --git a/remoting/client/pepper/pepper_plugin.cc b/remoting/client/pepper/pepper_plugin.cc new file mode 100644 index 0000000..4cb022f --- /dev/null +++ b/remoting/client/pepper/pepper_plugin.cc @@ -0,0 +1,22 @@ +/* + * 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/client/pepper/pepper_plugin.h" + +namespace pepper { + +PepperPlugin::PepperPlugin(NPNetscapeFuncs* browser_funcs, NPP instance) + : browser_funcs_(browser_funcs), + extensions_(NULL), + instance_(instance) { + browser_funcs_->getvalue(instance_, NPNVPepperExtensions, + static_cast<void*>(&extensions_)); +} + +PepperPlugin::~PepperPlugin() { +} + +} // namespace pepper diff --git a/remoting/client/pepper/pepper_plugin.h b/remoting/client/pepper/pepper_plugin.h new file mode 100644 index 0000000..723dc90 --- /dev/null +++ b/remoting/client/pepper/pepper_plugin.h @@ -0,0 +1,106 @@ +// 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_CLIENT_PEPPER_PEPPER_PLUGIN_H_ +#define REMOTING_CLIENT_PEPPER_PEPPER_PLUGIN_H_ + +#include "third_party/npapi/bindings/npapi.h" +#include "third_party/npapi/bindings/npapi_extensions.h" +#include "third_party/npapi/bindings/nphostapi.h" + +namespace pepper { + +class PepperPlugin { + public: + // This class stores information about the plugin that cannot be instantiated + // as part of the PepperPlugin class because it is required before the + // PepperPlugin has been created. + class Info { + public: + // True if these fields have been initialized. + bool initialized; + + // MIME type and description. + const char* mime_description; + + // Name of plugin (shown in about:plugins). + const char* plugin_name; + + // Short description of plugin (shown in about:plugins). + const char* plugin_description; + }; + + PepperPlugin(NPNetscapeFuncs* browser_funcs, NPP instance); + virtual ~PepperPlugin(); + + NPNetscapeFuncs* browser() const { return browser_funcs_; } + NPNExtensions* extensions() const { return extensions_; } + NPP instance() const { return instance_; } + + // Virtual methods to be implemented by the plugin subclass. + + virtual NPError New(NPMIMEType pluginType, int16 argc, + char* argn[], char* argv[]) { + return NPERR_GENERIC_ERROR; + } + + virtual NPError Destroy(NPSavedData** save) { + return NPERR_GENERIC_ERROR; + } + + virtual NPError SetWindow(NPWindow* window) { + return NPERR_GENERIC_ERROR; + } + + virtual NPError NewStream(NPMIMEType type, NPStream* stream, + NPBool seekable, uint16* stype) { + return NPERR_GENERIC_ERROR; + } + + virtual NPError DestroyStream(NPStream* stream, NPReason reason) { + return NPERR_GENERIC_ERROR; + } + + virtual void StreamAsFile(NPStream* stream, const char* fname) { + } + + virtual int32 WriteReady(NPStream* stream) { + return 0; + } + + virtual int32 Write(NPStream* stream, int32 offset, int32 len, void* buffer) { + return -1; + } + + virtual void Print(NPPrint* platformPrint) { + } + + virtual int16 HandleEvent(void* event) { + return false; + } + + virtual void URLNotify(const char* url, NPReason reason, void* nofifyData) { + } + + virtual NPError GetValue(NPPVariable variable, void* value) { + return NPERR_GENERIC_ERROR; + } + + virtual NPError SetValue(NPNVariable variable, void* value) { + return NPERR_GENERIC_ERROR; + } + + private: + // Browser callbacks. + NPNetscapeFuncs* browser_funcs_; + NPNExtensions* extensions_; + + NPP instance_; + + DISALLOW_COPY_AND_ASSIGN(PepperPlugin); +}; + +} // namespace pepper + +#endif // REMOTING_CLIENT_PEPPER_PEPPER_PLUGIN_H_ diff --git a/remoting/client/plugin/chromoting_main.cc b/remoting/client/plugin/chromoting_main.cc new file mode 100644 index 0000000..e2974bd --- /dev/null +++ b/remoting/client/plugin/chromoting_main.cc @@ -0,0 +1,24 @@ +// 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/client/plugin/chromoting_plugin.h" + +// Initialize general plugin info like name and description. +// This information needs to live outside of the PepperPlugin since it can +// be requested by the browser before the PepperPlugin has been instantiated. +void InitializePluginInfo(pepper::PepperPlugin::Info* plugin_info) { + plugin_info->mime_description = remoting::kMimeType; + plugin_info->plugin_name = "Chromoting"; + plugin_info->plugin_description = "Remote access for Chrome"; +} + +// Create the Pepper plugin instance. +// +// This is called in response to the NPP_New call. +// This instantiates a PepperPlugin subclass that implements the plugin +// specific functionality. +pepper::PepperPlugin* CreatePlugin(NPNetscapeFuncs* browser_funcs, + NPP instance) { + return new remoting::ChromotingPlugin(browser_funcs, instance); +} diff --git a/remoting/client/plugin/chromoting_plugin.cc b/remoting/client/plugin/chromoting_plugin.cc new file mode 100644 index 0000000..42b975b --- /dev/null +++ b/remoting/client/plugin/chromoting_plugin.cc @@ -0,0 +1,114 @@ +// 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/client/plugin/chromoting_plugin.h" + +#include "remoting/client/plugin/client.h" + +namespace remoting { + +ChromotingPlugin::ChromotingPlugin(NPNetscapeFuncs* browser_funcs, + NPP instance) + : PepperPlugin(browser_funcs, instance) { + device_ = extensions()->acquireDevice(instance, NPPepper2DDevice); + client_ = new ChromotingClient(this); +} + +ChromotingPlugin::~ChromotingPlugin() { +} + +NPError ChromotingPlugin::New(NPMIMEType pluginType, + int16 argc, char* argn[], char* argv[]) { + // Verify the mime type and subtype + std::string mime(kMimeType); + std::string::size_type type_end = mime.find("/"); + std::string::size_type subtype_end = mime.find(":", type_end); + if (strncmp(pluginType, kMimeType, subtype_end)) { + return NPERR_GENERIC_ERROR; + } + + // Extract the URL from the arguments. + char* url = NULL; + for (int i = 0; i < argc; ++i) { + if (strcmp(argn[i], "src") == 0) { + url = argv[i]; + break; + } + } + + if (!url) { + return NPERR_GENERIC_ERROR; + } + + return NPERR_NO_ERROR; +} + +NPError ChromotingPlugin::Destroy(NPSavedData** save) { + return NPERR_NO_ERROR; +} + +void ChromotingPlugin::draw() { + NPDeviceContext2D context; + NPDeviceContext2DConfig config; + device_->initializeContext(instance(), &config, &context); + + client_->draw(width_, height_, &context); + + device_->flushContext(instance(), &context, NULL, NULL); +} + +NPError ChromotingPlugin::SetWindow(NPWindow* window) { + width_ = window->width; + height_ = window->height; + + client_->set_window(); + + draw(); + + return NPERR_NO_ERROR; +} + +int16 ChromotingPlugin::HandleEvent(void* event) { + NPPepperEvent* npevent = static_cast<NPPepperEvent*>(event); + char ch; + + switch (npevent->type) { + case NPEventType_MouseDown: + // Fall through + case NPEventType_MouseUp: + // Fall through + case NPEventType_MouseMove: + // Fall through + case NPEventType_MouseEnter: + // Fall through + case NPEventType_MouseLeave: + client_->handle_mouse_event(npevent); + break; + case NPEventType_MouseWheel: + case NPEventType_RawKeyDown: + break; + case NPEventType_KeyDown: + case NPEventType_KeyUp: + break; + case NPEventType_Char: + client_->handle_char_event(npevent); + break; + case NPEventType_Minimize: + case NPEventType_Focus: + case NPEventType_Device: + break; + } + + return false; +} + +NPError ChromotingPlugin::GetValue(NPPVariable variable, void* value) { + return NPERR_NO_ERROR; +} + +NPError ChromotingPlugin::SetValue(NPNVariable variable, void* value) { + return NPERR_NO_ERROR; +} + +} // namespace remoting diff --git a/remoting/client/plugin/chromoting_plugin.h b/remoting/client/plugin/chromoting_plugin.h new file mode 100644 index 0000000..e2bc78f --- /dev/null +++ b/remoting/client/plugin/chromoting_plugin.h @@ -0,0 +1,53 @@ +// 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_CLIENT_PLUGIN_CHROMOTING_PLUGIN_H_ +#define REMOTING_CLIENT_PLUGIN_CHROMOTING_PLUGIN_H_ + +#include <string> + +#include "remoting/client/pepper/pepper_plugin.h" + +namespace remoting { + +static const char kMimeType[] + = "pepper-application/x-chromoting-plugin::Chromoting"; + +class ChromotingClient; + +class ChromotingPlugin : public pepper::PepperPlugin { + public: + ChromotingPlugin(NPNetscapeFuncs* browser_funcs, NPP instance); + virtual ~ChromotingPlugin(); + + int width() { return width_; } + int height() { return height_; } + NPDevice* device() { return device_; } + + NPError New(NPMIMEType pluginType, int16 argc, char* argn[], char* argv[]); + NPError Destroy(NPSavedData** save); + NPError SetWindow(NPWindow* window); + int16 HandleEvent(void* event); + NPError GetValue(NPPVariable variable, void* value); + NPError SetValue(NPNVariable variable, void* value); + + // Set up drawing context and update display. + void draw(); + + private: + // Size of the plugin window. + int width_, height_; + + // Rendering device provided by browser. + NPDevice* device_; + + // Chromoting client manager. + ChromotingClient* client_; + + DISALLOW_COPY_AND_ASSIGN(ChromotingPlugin); +}; + +} // namespace remoting + +#endif // REMOTING_CLIENT_PLUGIN_CHROMOTING_PLUGIN_H_ diff --git a/remoting/client/plugin/chromoting_plugin_test.html b/remoting/client/plugin/chromoting_plugin_test.html new file mode 100644 index 0000000..ddc53c2 --- /dev/null +++ b/remoting/client/plugin/chromoting_plugin_test.html @@ -0,0 +1,9 @@ +<html> +<head> +<title>Sample Pepper Plugin</title> +</head> +<body> +<object type="pepper-application/x-chromoting-plugin" width="1366" height="768" + src="chromotocol:garykac@chromoting.org" /> +</body> +</html> diff --git a/remoting/client/plugin/chromoting_plugin_unittest.cc b/remoting/client/plugin/chromoting_plugin_unittest.cc new file mode 100644 index 0000000..68baa9c --- /dev/null +++ b/remoting/client/plugin/chromoting_plugin_unittest.cc @@ -0,0 +1,93 @@ +// 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 "base/scoped_ptr.h" +#include "remoting/client/plugin/chromoting_plugin.h" +#include "remoting/client/pepper/fake_browser.h" +#include "testing/gtest/include/gtest/gtest.h" + +// Routine to create the PepperPlugin subclass that implements all of the +// plugin-specific functionality. +pepper::PepperPlugin* CreatePlugin(NPNetscapeFuncs* browser_funcs, + NPP instance); + + +class ChromotingPluginTest : public testing::Test { + protected: + + virtual void SetUp() { + // Set up the fake browser callback routines. + fake_browser_ = Singleton<FakeBrowser>::get(); + NPNetscapeFuncs* browser_funcs_ = fake_browser_->GetBrowserFuncs(); + instance_.reset(new NPP_t()); + + // Create the ChromotingPlugin for testing. + pepper::PepperPlugin* pepper_plugin; + pepper_plugin = CreatePlugin(browser_funcs_, instance_.get()); + plugin_.reset( + reinterpret_cast<remoting::ChromotingPlugin*>(pepper_plugin)); + } + + virtual void TearDown() { + } + + FakeBrowser* fake_browser_; + scoped_ptr<NPP_t> instance_; + scoped_ptr<remoting::ChromotingPlugin> plugin_; +}; + +TEST_F(ChromotingPluginTest, TestSetup) { + ASSERT_TRUE(plugin_->browser() != NULL); + ASSERT_TRUE(plugin_->extensions() != NULL); + ASSERT_TRUE(plugin_->instance() != NULL); + + ASSERT_TRUE(plugin_->device() != NULL); +} + +TEST_F(ChromotingPluginTest, TestNew) { + NPMIMEType mimetype = + const_cast<NPMIMEType>("pepper-application/x-chromoting-plugin"); + int16 argc; + char* argn[4]; + char* argv[4]; + NPError result; + + // Test 0 arguments (NULL arrays). + argc = 0; + result = plugin_->New(mimetype, argc, NULL, NULL); + ASSERT_EQ(NPERR_GENERIC_ERROR, result); + + // Test 0 arguments. + argc = 0; + result = plugin_->New(mimetype, argc, argn, argv); + ASSERT_EQ(NPERR_GENERIC_ERROR, result); + + // Test 1 argument (missing "src"). + argc = 1; + argn[0] = const_cast<char*>("noturl"); + argv[0] = const_cast<char*>("random.value"); + result = plugin_->New(mimetype, argc, argn, argv); + ASSERT_EQ(NPERR_GENERIC_ERROR, result); + + // Test "src" argument. + argc = 1; + argn[0] = const_cast<char*>("src"); + argv[0] = const_cast<char*>("chromotocol:name@chromoting.org"); + result = plugin_->New(mimetype, argc, argn, argv); + ASSERT_EQ(NPERR_NO_ERROR, result); +} + + +static uint32 get_pixel(uint32* pixels, int stride, int x, int y) { + return pixels[((x) + ((y) * (stride >> 2)))]; +} + +TEST_F(ChromotingPluginTest, TestSetWindow) { + NPWindow* window = fake_browser_->GetWindow(); + NPError result; + + result = plugin_->SetWindow(window); + ASSERT_EQ(NPERR_NO_ERROR, result); +} diff --git a/remoting/client/plugin/chromotocol.h b/remoting/client/plugin/chromotocol.h new file mode 100644 index 0000000..9a35351 --- /dev/null +++ b/remoting/client/plugin/chromotocol.h @@ -0,0 +1,79 @@ +// 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_CLIENT_PLUGIN_CHROMOTOCOL_H_ +#define REMOTING_CLIENT_PLUGIN_CHROMOTOCOL_H_ + +#include "base/scoped_ptr.h" + +namespace remoting { + +class HostConnection; + +enum ControlMessage { + MessageInit, + MessageUpdate, + MessageMouse, +}; + +struct InitMessage { + int message; + int compression; + int width; + int height; +}; + +struct MouseMessage { + int message; + int x, y; + int flags; +}; + +enum MouseFlag { + LeftDown = 1 << 1, + LeftUp = 1 << 2, + RightDown = 1 << 3, + RightUp = 1 << 4 +}; + +struct UpdateMessage { + int message; + int num_diffs; + int compression; + int compressed_size; +}; + +enum ImageFormat { + FormatRaw, + FormatJpeg, // Not used + FormatPng, // Not used + FormatZlib, // Not used + FormatVp8, +}; + +enum Compression { + CompressionNone, + CompressionZlib, +}; + +struct BinaryImageHeader { + BinaryImageHeader() + : format(FormatRaw), x(0), y(0), width(0), height(0), size(0) {} + + ImageFormat format; + int x; + int y; + int width; + int height; + int size; +}; + +struct BinaryImage { + BinaryImageHeader header; + scoped_ptr<char> data; +}; + +} // namespace remoting + +#endif // REMOTING_CLIENT_PLUGIN_CHROMOTOCOL_H_ diff --git a/remoting/client/plugin/client.cc b/remoting/client/plugin/client.cc new file mode 100644 index 0000000..5de4b5f --- /dev/null +++ b/remoting/client/plugin/client.cc @@ -0,0 +1,392 @@ +// 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/client/plugin/client.h" + +#include <string> +#include <iostream> // TODO(garykac): Remove this or replace with debug log. + +#include "base/logging.h" +#include "media/base/yuv_convert.h" +#include "remoting/client/plugin/chromoting_plugin.h" +#include "remoting/client/plugin/chromotocol.h" +#include "remoting/client/plugin/compression.h" +#include "remoting/client/plugin/decoder.h" +#include "remoting/client/plugin/host_connection.h" + +namespace remoting { + +ChromotingClient::ChromotingClient(ChromotingPlugin* plugin) { + plugin_ = plugin; + host_ = new HostConnection(); + verbose_ = true; +} + +ChromotingClient::~ChromotingClient() { +} + +void ChromotingClient::hexdump(void* ptr, int buflen) { + unsigned char* buf = static_cast<unsigned char*>(ptr); + int i, j; + for (int i = 0; i < buflen; i += 16) { + printf("%06x: ", i); + for (int j = 0; j < 16; j ++) + if ((i + j) < buflen) + printf("%02x ", buf[i + j]); + else + printf(" "); + printf(" "); + for (int j = 0; j < 16; j++) + if ((i + j) < buflen) + printf("%c", isprint(buf[i + j]) ? buf[i + j] : '.'); + printf("\n"); + } +} + +void ChromotingClient::merge_image(BinaryImageHeader* header, char* data) { + // Merge this image into the current image. + // Src bitmap starts at lower left. + int bytes_per_pixel = 3; + uint8* src_bitmap = reinterpret_cast<uint8*>(data); + int src_bytes_per_row = header->width * bytes_per_pixel; + + // Dst bitmap starts at lower left. + uint8* dst_bitmap = reinterpret_cast<uint8*>(screen_->data.get()); + dst_bitmap += ((header->y * screen_->header.width) + + header->x) * bytes_per_pixel; + int dst_bytes_per_row = screen_->header.width * bytes_per_pixel; + + for (int y = 0; y < header->height; ++y) { + memcpy(dst_bitmap, src_bitmap, src_bytes_per_row); + + src_bitmap += src_bytes_per_row; + dst_bitmap += dst_bytes_per_row; + } +} + +// Draw the current image into the given device context. +void ChromotingClient::draw(int width, int height, NPDeviceContext2D* context) { + if (screen_ != NULL) { + int max_width = width; + if (max_width > screen_->header.width) { + max_width = screen_->header.width; + } + int max_height = height; + if (max_height > screen_->header.height) { + max_height = screen_->header.height; + } + + // Src bitmap starts at lower left. + int bytes_per_pixel = 3; + int src_bytes_per_row = screen_->header.width * bytes_per_pixel; + uint8* src_bitmap = reinterpret_cast<uint8*>(screen_->data.get()); + src_bitmap += ((max_height - 1) * src_bytes_per_row); + uint8* src_row_start = reinterpret_cast<uint8*>(src_bitmap); + + // Dst bitmap (window) starts at upper left. + uint32* dst_bitmap = static_cast<uint32*>(context->region); + uint8* dst_row_start = reinterpret_cast<uint8*>(dst_bitmap); + int dst_bytes_per_row = context->stride; + + for (int y = 0; y < max_height; ++y) { + for (int x = 0; x < max_width; ++x) { + uint8 b = (*src_bitmap++ & 0xff); + uint8 g = (*src_bitmap++ & 0xff); + uint8 r = (*src_bitmap++ & 0xff); + uint8 alpha = 0xff; + *dst_bitmap++ = ((alpha << 24) | (r << 16) | (g << 8) | b); + } + src_row_start -= src_bytes_per_row; + src_bitmap = src_row_start; + dst_row_start += dst_bytes_per_row; + dst_bitmap = reinterpret_cast<uint32*>(dst_row_start); + } + } +} + +bool ChromotingClient::connect_to_host(const std::string ip) { + if (!host_->connect(ip.c_str())) { + return false; + } + + // Process init command. + InitMessage init_message; + host_->read_data(reinterpret_cast<char*>(&init_message), + sizeof(init_message)); + if (verbose_) { + std::cout << "Received message " << init_message.message << std::endl; + } + if (init_message.message != MessageInit) { + std::cout << "Expected MessageInit" << std::endl; + return false; + } + if (verbose_) { + std::cout << "Compression: " << init_message.compression << std::endl; + std::cout << "Width x height: " << init_message.width << " x " + << init_message.height << std::endl; + } + + screen_.reset(new BinaryImage()); + if (!read_image(screen_.get())) { + return false; + } + + plugin_->draw(); + return true; +} + +void ChromotingClient::print_host_ip_prompt() { + std::cout << "IP address of host machine: "; +} + +// This is called whenever the window changes geometry. +// Currently, we only worry about the first call so we can display our +// login prompt. +void ChromotingClient::set_window() { + if (!host_->connected()) { + print_host_ip_prompt(); + std::cout.flush(); + } +} + +// Process char input. +void ChromotingClient::handle_char_event(NPPepperEvent* npevent) { + if (!host_->connected()) { + handle_login_char(static_cast<char>(npevent->u.character.text[0])); + } +} + +// Process char input before the connection to the host has been made. +// This currently reads the IP address of the host but will eventually +// be changed to read GAIA login credentials. +// Later this will be removed once we have an appropriate web interface for +// discovering hosts. +void ChromotingClient::handle_login_char(char ch) { + if (ch == 0x0d) { + std::cout << std::endl; + if (host_ip_address_.length() == 0) { + host_ip_address_ = "172.31.11.205"; + } + if (!connect_to_host(host_ip_address_)) { + host_ip_address_ = ""; + std::cout << "Unable to connect to host!" << std::endl; + print_host_ip_prompt(); + } else { + std::cout << "Connected to " << host_ip_address_ << std::endl; + } + } else { + host_ip_address_ += ch; + std::cout << ch; + } + std::cout.flush(); +} + +// Process the Pepper mouse event. +void ChromotingClient::handle_mouse_event(NPPepperEvent* npevent) { + if (host_->connected()) { + send_mouse_message(npevent); + if (handle_update_message()) { + plugin_->draw(); + } + } +} + +// Pass the given Pepper mouse event along to the host. +void ChromotingClient::send_mouse_message(NPPepperEvent* event) { + NPMouseEvent* mouse_event = &event->u.mouse; + MouseMessage mouse_msg; + + mouse_msg.message = MessageMouse; + mouse_msg.x = mouse_event->x; + mouse_msg.y = mouse_event->y; + + mouse_msg.flags = 0; + int32 type = event->type; + int32 button = mouse_event->button; + if (type == NPEventType_MouseDown) { + if (button == NPMouseButton_Left) { + mouse_msg.flags |= LeftDown; + } else if (button == NPMouseButton_Right) { + mouse_msg.flags |= RightDown; + } + } else if (type == NPEventType_MouseUp) { + if (button == NPMouseButton_Left) { + mouse_msg.flags |= LeftUp; + } else if (button == NPMouseButton_Right) { + mouse_msg.flags |= RightUp; + } + } + host_->write_data((const char*)&mouse_msg, sizeof(mouse_msg)); +} + +// Process the pending update command from the host. +// Return true if the screen image has been updated. +bool ChromotingClient::handle_update_message() { + UpdateMessage update_message; + int result = host_->read_data(reinterpret_cast<char*>(&update_message), + sizeof(update_message)); + if (!result) { + std::cout << "Failed to get update command" << std::endl; + return false; + } + + if (update_message.message != MessageUpdate) { + std::cout << "Expected MessageUpdate" << std::endl; + return false; + } + + if (verbose_) { + std::cout << "message: " << update_message.message << std::endl; + } + + if (update_message.compression == CompressionZlib) { + // Read all data. + ZDecompressor decomp; + char buffer[4096]; + int size = update_message.compressed_size; + while (size > 0) { + // Determine how much we should read from network. + int read = std::min(static_cast<int>(sizeof(buffer)), size); + result = host_->read_data(buffer, read); + decomp.Write(buffer, read); + size -= read; + } + decomp.Flush(); + + // Decompress raw image data and break into individual images. + char* raw_buffer = decomp.GetRawData(); + int raw_size = decomp.GetRawSize(); + int read = 0; + BinaryImageHeader header; + while (read < raw_size) { + memcpy(&header, raw_buffer, sizeof(BinaryImageHeader)); + if (!check_image_header(&header)) { + return false; + } + read += sizeof(BinaryImageHeader); + raw_buffer += sizeof(BinaryImageHeader); + + // Merge this image fragment into the screen bitmap. + merge_image(&header, raw_buffer); + + read += header.size; + raw_buffer += header.size; + } + } else if (update_message.compression == CompressionNone) { + printf("compressionNone\n"); + for (int i = 0; i < update_message.num_diffs; i++) { + BinaryImage* image = new BinaryImage(); + read_image(image); + + // Merge this image update into the screen image. + merge_image(&image->header, image->data.get()); + + delete image; + } + } else { + return false; + } + + return true; +} + +// Check the validity of the image header. +bool ChromotingClient::check_image_header(BinaryImageHeader* header) { + if (header == NULL) { + std::cout << "Invalid image" << std::endl; + return false; + } + + if (header->format != FormatRaw && header->format != FormatVp8) { + std::cout << "Wrong image format : " << header->format << std::endl; + return false; + } + + if (verbose_) { + std::cout << "Image:" << std::endl; + std::cout << " Format " << header->format << std::endl; + std::cout << " X,Y " << header->x << ", " + << header->y << std::endl; + std::cout << " WxH " << header->width << " x " + << header->height << std::endl; + std::cout << " Size " << header->size << std::endl; + } + + return true; +} + +// Read an image from the host and store it in the given BinaryImage. +bool ChromotingClient::read_image(BinaryImage* image) { + int result = host_->read_data(reinterpret_cast<char*>(&image->header), + sizeof(image->header)); + if (!result) { + std::cout << "Failed to receive image header" << std::endl; + return false; + } + + if (!check_image_header(&image->header)) { + return false; + } + + char* raw_data = new char[image->header.size]; + result = host_->read_data(raw_data, image->header.size); + if (!result) { + std::cout << "Failed to receive image data" << std::endl; + return false; + } + + if (image->header.format == FormatRaw) { + // Raw image - all we need to do is load the data, so we're done. + image->data.reset(raw_data); + return true; + } else if (image->header.format == FormatVp8) { + return false; + // TODO(hclam): Enable this block of code when we have VP8. +#if 0 + // Vp8 encoded - need to convert YUV image data to RGB. + static VP8VideoDecoder decoder; + uint8* planes[3]; + int strides[3]; + printf("decoder.DecodeFrame\n"); + if (!decoder.DecodeFrame(raw_data, image->header.size)) { + std::cout << "Unable to decode frame" << std::endl; + return false; + } + printf("decoder.GetDecodedFrame\n"); + if (!decoder.GetDecodedFrame(reinterpret_cast<char**>(planes), strides)) { + std::cout << "Unable to get decoded frame" << std::endl; + return false; + } + printf("width = %d\n", image->header.width); + for (int i=0; i<3; i++) { + printf("stride[%d] = %d\n", i, strides[0]); + } + + // Convert YUV to RGB. + int width = image->header.width; + int height = image->header.height; + char* rgb_data = new char[width * height * sizeof(int32)]; + printf("ConvertYUVToRGB32\n"); + ConvertYUVToRGB32(planes[0], planes[1], planes[2], + reinterpret_cast<uint8*>(rgb_data), + width, + image->header.height, + width, // y stride, + width / 2, // uv stride, + width * sizeof(int32), // rgb stride + media::YV12); + printf("conversion done\n"); + + image->data.reset(rgb_data); + + // Raw YUV data is no longer needed. + delete raw_data; + return true; +#endif + } + + return false; +} + +} // namespace remoting diff --git a/remoting/client/plugin/client.h b/remoting/client/plugin/client.h new file mode 100644 index 0000000..8772a6e --- /dev/null +++ b/remoting/client/plugin/client.h @@ -0,0 +1,66 @@ +// 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_CLIENT_PLUGIN_CLIENT_H_ +#define REMOTING_CLIENT_PLUGIN_CLIENT_H_ + +#include <string> + +#include "base/scoped_ptr.h" +#include "remoting/client/plugin/chromoting_plugin.h" + +namespace remoting { + +class BinaryImage; +class BinaryImageHeader; +class HostConnection; + +class ChromotingClient { + public: + explicit ChromotingClient(ChromotingPlugin* plugin); + virtual ~ChromotingClient(); + + void hexdump(void *ptr, int buflen); + + void merge_image(BinaryImageHeader* header, char* data); + void draw(int width, int height, NPDeviceContext2D* context); + + bool connect_to_host(const std::string ip); + void print_host_ip_prompt(); + + void set_window(); + + void handle_char_event(NPPepperEvent* npevent); + void handle_login_char(char ch); + + void handle_mouse_event(NPPepperEvent* npevent); + void send_mouse_message(NPPepperEvent* event); + + bool handle_update_message(); + + bool check_image_header(BinaryImageHeader* header); + bool read_image(BinaryImage* image); + + private: + // Pepper plugin (communicate with browser). + ChromotingPlugin* plugin_; + + // Network connection (communicate with remote host machine). + HostConnection* host_; + + // IP address of remote host machine. + std::string host_ip_address_; + + // Screen bitmap image. + scoped_ptr<BinaryImage> screen_; + + // Display extended output messages (for debugging). + bool verbose_; + + DISALLOW_COPY_AND_ASSIGN(ChromotingClient); +}; + +} // namespace remoting + +#endif // REMOTING_CLIENT_PLUGIN_CLIENT_H_ diff --git a/remoting/client/plugin/compression.cc b/remoting/client/plugin/compression.cc new file mode 100644 index 0000000..4e89727 --- /dev/null +++ b/remoting/client/plugin/compression.cc @@ -0,0 +1,114 @@ +// 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/client/plugin/compression.h" + +#include <assert.h> + +namespace remoting { + +static const int kZLIB_CHUNK = 256 * 1024; + +ZCompressor::ZCompressor() { + stream_.zalloc = Z_NULL; + stream_.zfree = Z_NULL; + stream_.opaque = Z_NULL; + + deflateInit(&stream_, Z_BEST_SPEED); +} + +void ZCompressor::WriteInternal(char* buffer, int size, int flush) { + stream_.avail_in = size; + stream_.next_in = reinterpret_cast<Bytef*>(buffer); + + // Expand the internal buffer. + while (true) { + int last_size = buffer_.size(); + buffer_.resize(last_size + kZLIB_CHUNK); + stream_.avail_out = kZLIB_CHUNK; + stream_.next_out = reinterpret_cast<Bytef*>(&buffer_[last_size]); + int ret = deflate(&stream_, flush); + assert(ret != Z_STREAM_ERROR); + + // Shrink the size of the vector. It doesn't alter the capacity. + int compressed = kZLIB_CHUNK - stream_.avail_out; + buffer_.resize(last_size + compressed); + if (!compressed) + break; + } +} + +void ZCompressor::Write(char* buffer, int size) { + WriteInternal(buffer, size, Z_NO_FLUSH); +} + +void ZCompressor::Flush() { + WriteInternal(NULL, 0, Z_FINISH); + deflateEnd(&stream_); +} + +int ZCompressor::GetCompressedSize() { + return buffer_.size(); +} + +char* ZCompressor::GetCompressedData() { + return &buffer_[0]; +} + +int ZCompressor::GetRawSize() { + // I don't care about this. + return 0; +} + +ZDecompressor::ZDecompressor() { + stream_.zalloc = Z_NULL; + stream_.zfree = Z_NULL; + stream_.opaque = Z_NULL; + stream_.avail_in = 0; + stream_.next_in = Z_NULL; + + inflateInit(&stream_); +} + +void ZDecompressor::WriteInternal(char* buffer, int size, int flush) { + stream_.avail_in = size; + stream_.next_in = reinterpret_cast<Bytef*>(buffer); + + while (true) { + int last_size = buffer_.size(); + buffer_.resize(last_size + kZLIB_CHUNK); + stream_.avail_out = kZLIB_CHUNK; + stream_.next_out = reinterpret_cast<Bytef*>(&buffer_[last_size]); + int ret = inflate(&stream_, flush); + assert(ret != Z_STREAM_ERROR); + + // Shrink the size of the vector. It doesn't alter the capacity. + int decompressed = kZLIB_CHUNK - stream_.avail_out; + buffer_.resize(last_size + decompressed); + if (!decompressed) + break; + } +} + +void ZDecompressor::Write(char* buffer, int size) { + WriteInternal(buffer, size, Z_NO_FLUSH); +} + +void ZDecompressor::Flush() { + inflateEnd(&stream_); +} + +char* ZDecompressor::GetRawData() { + return &buffer_[0]; +} + +int ZDecompressor::GetRawSize() { + return buffer_.size(); +} + +int ZDecompressor::GetCompressedSize() { + // I don't care. +} + +} // namespace remoting diff --git a/remoting/client/plugin/compression.h b/remoting/client/plugin/compression.h new file mode 100644 index 0000000..8d4ee49 --- /dev/null +++ b/remoting/client/plugin/compression.h @@ -0,0 +1,90 @@ +// 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_CLIENT_PLUGIN_COMPRESSION_H_ +#define REMOTING_CLIENT_PLUGIN_COMPRESSION_H_ + +#include <vector> + +#include "base/basictypes.h" + +#if defined(USE_SYSTEM_ZLIB) +#include <zlib.h> +#else +#include "third_party/zlib/zlib.h" +#endif + +namespace remoting { + +class Compressor { + public: + Compressor() {} + virtual ~Compressor() {} + + virtual void Write(char* buffer, int size) = 0; + virtual void Flush() = 0; + virtual int GetCompressedSize() = 0; + virtual char* GetCompressedData() = 0; + virtual int GetRawSize() = 0; + + DISALLOW_COPY_AND_ASSIGN(Compressor); +}; + +class Decompressor { + public: + Decompressor() {} + virtual ~Decompressor() {} + + virtual void Write(char* buffer, int size) = 0; + virtual void Flush() = 0; + virtual char* GetRawData() = 0; + virtual int GetRawSize() = 0; + virtual int GetCompressedSize() = 0; + + DISALLOW_COPY_AND_ASSIGN(Decompressor); +}; + +class ZCompressor : public Compressor { + public: + ZCompressor(); + virtual ~ZCompressor() {} + + virtual void Write(char* buffer, int size); + virtual void Flush(); + virtual int GetCompressedSize(); + virtual char* GetCompressedData(); + virtual int GetRawSize(); + + private: + void WriteInternal(char* buffer, int size, int flush); + + std::vector<char> buffer_; + z_stream stream_; + + DISALLOW_COPY_AND_ASSIGN(ZCompressor); +}; + +class ZDecompressor : public Decompressor { + public: + ZDecompressor(); + virtual ~ZDecompressor() {} + + virtual void Write(char* buffer, int size); + virtual void Flush(); + virtual char* GetRawData(); + virtual int GetRawSize(); + virtual int GetCompressedSize(); + + private: + void WriteInternal(char* buffer, int size, int flush); + + std::vector<char> buffer_; + z_stream stream_; + + DISALLOW_COPY_AND_ASSIGN(ZDecompressor); +}; + +} // namespace remoting + +#endif // REMOTING_CLIENT_PLUGIN_COMPRESSION_H_ diff --git a/remoting/client/plugin/decoder.h b/remoting/client/plugin/decoder.h new file mode 100644 index 0000000..7c82723 --- /dev/null +++ b/remoting/client/plugin/decoder.h @@ -0,0 +1,39 @@ +// 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. + +// TODO(hclam): Enable this when we have VP8. +// extern "C" { +// #include "remoting/demo/third_party/on2/include/on2_decoder.h" +// #include "remoting/demo/third_party/on2/include/vp8dx.h" +// } + +class Stream; + +class VideoDecoder { + public: + virtual ~VideoDecoder() {} + virtual bool DecodeFrame(char* buffer, int size) = 0; + virtual bool GetDecodedFrame(char** planes, int* strides) = 0; + virtual bool IsInitialized() = 0; + virtual int GetWidth() = 0; + virtual int GetHeight() = 0; + virtual int GetFormat() = 0; +}; + +// TODO(hclam): Enable this when we have VP8. +// class VP8VideoDecoder { +// public: +// VP8VideoDecoder(); +// virtual bool DecodeFrame(char* buffer, int size); +// virtual bool GetDecodedFrame(char** planes, int* strides); +// virtual bool IsInitialized(); +// virtual int GetWidth(); +// virtual int GetHeight(); +// virtual int GetFormat(); + +// private: +// on2_codec_ctx_t codec_; +// on2_codec_iter_t iter_; +// bool first_frame_; +// }; diff --git a/remoting/client/plugin/host_connection.cc b/remoting/client/plugin/host_connection.cc new file mode 100644 index 0000000..fff9526 --- /dev/null +++ b/remoting/client/plugin/host_connection.cc @@ -0,0 +1,89 @@ +// 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/client/plugin/host_connection.h" + +#include <netinet/in.h> +#include <netdb.h> +#include <sys/socket.h> + +#include <iostream> // TODO(garykac): Replace or remove debug output. + +#include "remoting/client/plugin/chromoting_plugin.h" + +namespace remoting { + +HostConnection::HostConnection() { + connected_ = false; +} + +HostConnection::~HostConnection() { +} + +bool HostConnection::connect(const char* ip_address) { + std::cout << "Attempting to connect to " << ip_address << std::endl; + + sock_ = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (sock_ < 0) { + std::cout << "Can't open socket" << std::endl; + return false; + } + + hostent* server = gethostbyname(ip_address); + if (!server) { + std::cout << "Can't resolve address" << std::endl; + return false; + } + + sockaddr_in server_address; + memset(&server_address, 0, sizeof(server_address)); + server_address.sin_family = AF_INET; + memcpy(&server_address.sin_addr.s_addr, server->h_addr, server->h_length); + server_address.sin_port = htons(4000); + + if (::connect(sock_, reinterpret_cast<sockaddr*>(&server_address), + sizeof(server_address)) < 0) { + std::cout << "Cannot connect to server" << std::endl; + return false; + } + + connected_ = true; + return true; +} + +// Read data from a socket to a buffer. +bool HostConnection::read_data(char* buffer, int num_bytes) { + if (!connected_) { + return false; + } + + while (num_bytes > 0) { + int num_bytes_read = read(sock_, buffer, num_bytes); + if (num_bytes_read <= 0) { + return false; + } + buffer += num_bytes_read; + num_bytes -= num_bytes_read; + } + return true; +} + +// Write data from a buffer to a socket. +bool HostConnection::write_data(const char* buffer, int num_bytes) { + if (!connected_) { + return false; + } + + while (num_bytes > 0) { + int num_bytes_written = write(sock_, buffer, num_bytes); + if (num_bytes_written <= 0) { + return false; + } + num_bytes -= num_bytes_written; + buffer += num_bytes_written; + } + return true; +} + +} // namespace remoting diff --git a/remoting/client/plugin/host_connection.h b/remoting/client/plugin/host_connection.h new file mode 100644 index 0000000..7dc4004 --- /dev/null +++ b/remoting/client/plugin/host_connection.h @@ -0,0 +1,37 @@ +// 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_CLIENT_PLUGIN_HOST_CONNECTION_H_ +#define REMOTING_CLIENT_PLUGIN_HOST_CONNECTION_H_ + +#include "base/basictypes.h" + +namespace remoting { + +class HostConnection { + public: + HostConnection(); + virtual ~HostConnection(); + + bool connect(const char* ip_address); + bool connected() { + return connected_; + } + + bool read_data(char *buffer, int num_bytes); + bool write_data(const char* buffer, int num_bytes); + + private: + // True if we have a valid connection. + bool connected_; + + // The socket for the connection. + int sock_; + + DISALLOW_COPY_AND_ASSIGN(HostConnection); +}; + +} // namespace remoting + +#endif // REMOTING_CLIENT_PLUGIN_HOST_CONNECTION_H_ diff --git a/remoting/client/simple_client.cc b/remoting/client/simple_client.cc new file mode 100644 index 0000000..ff74312 --- /dev/null +++ b/remoting/client/simple_client.cc @@ -0,0 +1,196 @@ +// 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. +// +// A simple client implements a minimalize Chromoting client and shows +// network traffic for debugging. + +#include <iostream> +#include <list> + +#include "base/at_exit.h" +#include "base/message_loop.h" +#include "base/stl_util-inl.h" +#include "media/base/data_buffer.h" +#include "remoting/base/protocol_decoder.h" +#include "remoting/client/host_connection.h" +#include "remoting/jingle_glue/jingle_channel.h" +#include "remoting/jingle_glue/jingle_client.h" + +using chromotocol_pb::HostMessage; +using chromotocol_pb::InitClientMessage; +using chromotocol_pb::BeginUpdateStreamMessage; +using chromotocol_pb::EndUpdateStreamMessage; +using chromotocol_pb::UpdateStreamPacketMessage; +using remoting::HostConnection; +using remoting::HostMessageList; +using remoting::JingleClient; +using remoting::JingleChannel; +using remoting::ProtocolDecoder; + +void SetConsoleEcho(bool on) { +#ifdef WIN32 + 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); +#else + if (on) + system("stty echo"); + else + system("stty -echo"); +#endif +} + +class SimpleHostEventHandler : public HostConnection::EventHandler { + public: + SimpleHostEventHandler(MessageLoop* loop) + : main_loop_(loop) { + } + + virtual void HandleMessages(HostConnection* conn, + HostMessageList* messages) { + HostMessageList list; + messages->swap(list); + for (size_t i = 0; i < list.size(); ++i) { + HostMessage* msg = list[i]; + if (msg->has_init_client()) { + HandleInitClientMessage(msg); + } else if (msg->has_begin_update_stream()) { + HandleBeginUpdateStreamMessage(msg); + } else if (msg->has_update_stream_packet()) { + HandleUpdateStreamPacketMessage(msg); + } else if (msg->has_end_update_stream()) { + HandleEndUpdateStreamMessage(msg); + } + } + STLDeleteElements<HostMessageList>(&list); + } + + virtual void OnConnectionOpened(HostConnection* conn) { + std::cout << "Connection establised." << std::endl; + } + + virtual void OnConnectionClosed(HostConnection* conn) { + std::cout << "Connection closed." << std::endl; + + // Quit the message if the connection has closed. + DCHECK(main_loop_); + main_loop_->PostTask(FROM_HERE, new MessageLoop::QuitTask()); + } + + virtual void OnConnectionFailed(HostConnection* conn) { + std::cout << "Conection failed." << std::endl; + + // Quit the message if the connection has failed. + DCHECK(main_loop_); + main_loop_->PostTask(FROM_HERE, new MessageLoop::QuitTask()); + } + + private: + void HandleInitClientMessage(HostMessage* host_msg) { + const InitClientMessage& msg = host_msg->init_client(); + std::cout << "InitClient (" << msg.width() + << ", " << msg.height() << ")" << std::endl; + } + + void HandleBeginUpdateStreamMessage(HostMessage* host_msg) { + const BeginUpdateStreamMessage& msg = host_msg->begin_update_stream(); + } + + void HandleUpdateStreamPacketMessage(HostMessage* host_msg) { + const UpdateStreamPacketMessage& msg = host_msg->update_stream_packet(); + std::cout << "UpdateStreamPacket (" << msg.header().x() + << ", " << msg.header().y() << ") [" + << msg.header().width() << " x " << msg.header().height() << "]" + << std::endl; + } + + void HandleEndUpdateStreamMessage(HostMessage* host_msg) { + const EndUpdateStreamMessage& msg = host_msg->end_update_stream(); + } + + // JingleClient::Callback interface. + void OnStateChange(JingleClient* client, JingleClient::State state) { + if (state == JingleClient::CONNECTED) { + LOG(INFO) << "Connected as: " << client->GetFullJid(); + } else if (state == JingleClient::CLOSED) { + LOG(INFO) << "Connection closed."; + } + } + + MessageLoop* main_loop_; + + DISALLOW_COPY_AND_ASSIGN(SimpleHostEventHandler); +}; + +int main(int argc, char** argv) { + base::AtExitManager exit_manager; + + if (argc > 2) { + std::cerr << "Usage: " << argv[0] << " [<host_jid>]" << std::endl; + return 1; + } + + // Get host JID from command line arguments, or stdin if not specified. + std::string host_jid; + if (argc == 2) { + host_jid = argv[1]; + } else { + std::cout << "Host JID: "; + std::cin >> host_jid; + std::cin.ignore(); // Consume the leftover '\n' + } + if (host_jid.find("/chromoting") == std::string::npos) { + std::cerr << "Error: Expected Host JID in format: <jid>/chromoting<id>" + << std::endl; + return 1; + } + + // Get username (JID). + // Extract default JID from host_jid. + std::string username; + std::string default_username; + size_t jid_end = host_jid.find('/'); + if (jid_end != std::string::npos) { + default_username = host_jid.substr(0, jid_end); + } + std::cout << "JID [" << default_username << "]: "; + getline(std::cin, username); + if (username.length() == 0) { + username = default_username; + } + if (username.length() == 0) { + std::cerr << "Error: Expected valid JID username" << std::endl; + return 1; + } + + // Get password (with console echo turned off). + std::string password; + SetConsoleEcho(false); + std::cout << "Password: "; + getline(std::cin, password); + SetConsoleEcho(true); + std::cout << std::endl; + + // The message loop that everything runs on. + MessageLoop main_loop; + SimpleHostEventHandler handler(&main_loop); + HostConnection connection(new ProtocolDecoder(), &handler); + connection.Connect(username, password, host_jid); + + // Run the message. + main_loop.Run(); + return 0; +} 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; +} diff --git a/remoting/jingle_glue/iq_request.cc b/remoting/jingle_glue/iq_request.cc new file mode 100644 index 0000000..b9fd6bf --- /dev/null +++ b/remoting/jingle_glue/iq_request.cc @@ -0,0 +1,79 @@ +// 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/jingle_glue/iq_request.h" + +#include "base/logging.h" +#include "base/message_loop.h" +#include "base/scoped_ptr.h" +#include "remoting/jingle_glue/jingle_client.h" +#include "talk/xmpp/constants.h" +#include "talk/xmpp/xmppengine.h" + +namespace remoting { + +IqRequest::IqRequest(JingleClient* jingle_client) + : jingle_client_(jingle_client), + cookie_(NULL) { + DCHECK(jingle_client_ != NULL); + DCHECK(MessageLoop::current() == jingle_client_->message_loop()); +} + +IqRequest::~IqRequest() { + DCHECK(MessageLoop::current() == jingle_client_->message_loop()); + Unregister(); +} + +void IqRequest::SendIq(const std::string& type, + const std::string& addressee, + buzz::XmlElement* iq_body) { + DCHECK(MessageLoop::current() == jingle_client_->message_loop()); + + // Unregister the handler if it is already registered. + Unregister(); + + DCHECK(type.length() > 0); + DCHECK(addressee.length() > 0); + + buzz::XmppClient* xmpp_client = jingle_client_->xmpp_client(); + DCHECK(xmpp_client); // Expect that connection is active. + + scoped_ptr<buzz::XmlElement> stanza(MakeIqStanza(type, addressee, iq_body, + xmpp_client->NextId())); + + xmpp_client->engine()->SendIq(stanza.get(), this, &cookie_); +} + +// static +buzz::XmlElement* IqRequest::MakeIqStanza(const std::string& type, + const std::string& addressee, + buzz::XmlElement* iq_body, + const std::string& id) { + buzz::XmlElement* stanza = new buzz::XmlElement(buzz::QN_IQ); + stanza->AddAttr(buzz::QN_TYPE, type); + stanza->AddAttr(buzz::QN_TO, addressee); + stanza->AddAttr(buzz::QN_ID, id); + stanza->AddElement(iq_body); + return stanza; +} + +void IqRequest::Unregister() { + if (cookie_) { + buzz::XmppClient* xmpp_client = jingle_client_->xmpp_client(); + // No need to unregister the handler if the client has been destroyed. + if (xmpp_client) { + xmpp_client->engine()->RemoveIqHandler(cookie_, NULL); + } + cookie_ = NULL; + } +} + +void IqRequest::IqResponse(buzz::XmppIqCookie cookie, + const buzz::XmlElement* stanza) { + if (callback_.get() != NULL) { + callback_->Run(stanza); + } +} + +} // namespace remoting diff --git a/remoting/jingle_glue/iq_request.h b/remoting/jingle_glue/iq_request.h new file mode 100644 index 0000000..1e9e764 --- /dev/null +++ b/remoting/jingle_glue/iq_request.h @@ -0,0 +1,65 @@ +// 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_JINGLE_GLUE_IQ_REQUEST_H_ +#define REMOTING_JINGLE_GLUE_IQ_REQUEST_H_ + +#include <string> + +#include "base/callback.h" +#include "talk/xmpp/xmppengine.h" +#include "testing/gtest/include/gtest/gtest_prod.h" + +namespace remoting { + +class JingleClient; + +// IqRequest class can be used to send an IQ stanza and then receive reply +// stanza for that request. It sends outgoing stanza when SendIq() is called, +// after that it forwards incoming reply stanza to the callback set with +// set_callback(). If multiple IQ stanzas are send with SendIq() then only reply +// to the last one will be received. +// The class must be used on the jingle thread only. +// TODO(sergeyu): Implement unittests for this class. +class IqRequest : private buzz::XmppIqHandler { + public: + typedef Callback1<const buzz::XmlElement*>::Type ReplyCallback; + + explicit IqRequest(JingleClient* jingle_client); + virtual ~IqRequest(); + + // Sends stanza of type |type| to |addressee|. |iq_body| contains body of + // the stanza. Ownership of |iq_body| is transfered to IqRequest. Must + // be called on the jingle thread. + void SendIq(const std::string& type, const std::string& addressee, + buzz::XmlElement* iq_body); + + // Sets callback that is called when reply stanza is received. Callback + // is called on the jingle thread. + void set_callback(ReplyCallback* callback) { + callback_.reset(callback); + } + + private: + FRIEND_TEST(IqRequestTest, MakeIqStanza); + + // XmppIqHandler interface. + virtual void IqResponse(buzz::XmppIqCookie cookie, + const buzz::XmlElement* stanza); + + static buzz::XmlElement* MakeIqStanza(const std::string& type, + const std::string& addressee, + buzz::XmlElement* iq_body, + const std::string& id); + + void Unregister(); + + scoped_refptr<JingleClient> jingle_client_; + buzz::XmppIqCookie cookie_; + scoped_ptr<ReplyCallback> callback_; +}; + +} // namespace remoting + +#endif // REMOTING_JINGLE_GLUE_IQ_REQUEST_H_ diff --git a/remoting/jingle_glue/iq_request_unittest.cc b/remoting/jingle_glue/iq_request_unittest.cc new file mode 100644 index 0000000..88a6033 --- /dev/null +++ b/remoting/jingle_glue/iq_request_unittest.cc @@ -0,0 +1,40 @@ +// 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/ref_counted.h" +#include "base/string_util.h" +#include "media/base/data_buffer.h" +#include "remoting/jingle_glue/iq_request.h" +#include "talk/xmllite/xmlelement.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace remoting { + +TEST(IqRequestTest, MakeIqStanza) { + const char* kMessageId = "0"; + const char* kNamespace = "chromium:testns"; + const char* kNamespacePrefix = "tes"; + const char* kBodyTag = "test"; + const char* kType = "get"; + const char* kTo = "user@domain.com"; + + std::string expected_xml_string = + StringPrintf( + "<cli:iq type=\"%s\" to=\"%s\" id=\"%s\" " + "xmlns:cli=\"jabber:client\">" + "<%s:%s xmlns:%s=\"%s\"/>" + "</cli:iq>", + kType, kTo, kMessageId, kNamespacePrefix, kBodyTag, + kNamespacePrefix, kNamespace); + + buzz::XmlElement* iq_body = + new buzz::XmlElement(buzz::QName(kNamespace, kBodyTag)); + scoped_ptr<buzz::XmlElement> stanza( + IqRequest::MakeIqStanza(kType, kTo, iq_body, kMessageId)); + + EXPECT_EQ(expected_xml_string, stanza->Str()); +} + +} // namespace remoting diff --git a/remoting/jingle_glue/jingle_channel.cc b/remoting/jingle_glue/jingle_channel.cc new file mode 100644 index 0000000..8111985 --- /dev/null +++ b/remoting/jingle_glue/jingle_channel.cc @@ -0,0 +1,210 @@ +// 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/jingle_glue/jingle_channel.h" + +#include "base/lock.h" +#include "base/logging.h" +#include "base/message_loop.h" +#include "base/waitable_event.h" +#include "media/base/data_buffer.h" +#include "remoting/jingle_glue/jingle_thread.h" +#include "talk/base/stream.h" + +using media::DataBuffer; + +namespace remoting { + +const size_t kReadBufferSize = 4096; + +JingleChannel::JingleChannel(Callback* callback) + : state_(INITIALIZING), + event_handler_(this), + callback_(callback), + write_buffer_size_(0), + current_write_buf_pos_(0) { + DCHECK(callback_ != NULL); +} + +// This constructor is only used in unit test. +JingleChannel::JingleChannel() + : state_(CLOSED), + write_buffer_size_(0), + current_write_buf_pos_(0) { +} + +JingleChannel::~JingleChannel() { + DCHECK(state_ == CLOSED); +} + +void JingleChannel::Init(JingleThread* thread, + talk_base::StreamInterface* stream, + const std::string& jid) { + thread_ = thread; + stream_.reset(stream); + stream_->SignalEvent.connect(&event_handler_, &EventHandler::OnStreamEvent); + + // Initialize |state_|. + switch (stream->GetState()) { + case talk_base::SS_CLOSED: + SetState(CLOSED); + break; + case talk_base::SS_OPENING: + SetState(CONNECTING); + break; + case talk_base::SS_OPEN: + SetState(OPEN); + // Try to read in case there is something in the stream. + thread_->message_loop()->PostTask( + FROM_HERE, NewRunnableMethod(this, &JingleChannel::DoRead)); + break; + default: + NOTREACHED(); + } + + jid_ = jid; +} + +void JingleChannel::Write(scoped_refptr<DataBuffer> data) { + // Discard empty packets. + if (data->GetDataSize() != 0) { + AutoLock auto_lock(write_lock_); + write_queue_.push_back(data); + write_buffer_size_ += data->GetDataSize(); + // Post event so that the data gets written in the tunnel thread. + thread_->message_loop()->PostTask( + FROM_HERE, NewRunnableMethod(this, &JingleChannel::DoWrite)); + } +} + +void JingleChannel::DoRead() { + while (true) { + size_t bytes_to_read; + if (stream_->GetAvailable(&bytes_to_read)) { + // Return immediately if we know there is nothing to read. + if (bytes_to_read == 0) + return; + } else { + // Try to read kReadBufferSize if the stream doesn't support + // GetAvailable(). + bytes_to_read = kReadBufferSize; + } + + scoped_refptr<DataBuffer> buffer( + new DataBuffer(new uint8[bytes_to_read], kReadBufferSize)); + size_t bytes_read; + int error; + talk_base::StreamResult result = stream_->Read( + buffer->GetWritableData(), bytes_to_read, &bytes_read, &error); + switch (result) { + case talk_base::SR_SUCCESS: { + DCHECK(bytes_read > 0); + buffer->SetDataSize(bytes_read); + callback_->OnPacketReceived(this, buffer); + break; + } + case talk_base::SR_BLOCK: { + return; + } + case talk_base::SR_EOS: { + SetState(CLOSED); + return; + } + case talk_base::SR_ERROR: { + SetState(FAILED); + return; + } + } + } +} + +void JingleChannel::DoWrite() { + while (true) { + if (!current_write_buf_) { + AutoLock auto_lock(write_lock_); + if (write_queue_.empty()) + break; + current_write_buf_ = write_queue_.front(); + current_write_buf_pos_ = 0; + write_queue_.pop_front(); + } + + size_t bytes_written; + int error; + talk_base::StreamResult result = stream_->Write( + current_write_buf_->GetData() + current_write_buf_pos_, + current_write_buf_->GetDataSize() - current_write_buf_pos_, + &bytes_written, &error); + switch (result) { + case talk_base::SR_SUCCESS: { + current_write_buf_pos_ += bytes_written; + if (current_write_buf_pos_ >= current_write_buf_->GetDataSize()) + current_write_buf_ = NULL; + { + AutoLock auto_lock(write_lock_); + write_buffer_size_ -= bytes_written; + } + break; + } + case talk_base::SR_BLOCK: { + return; + } + case talk_base::SR_EOS: { + SetState(CLOSED); + return; + } + case talk_base::SR_ERROR: { + SetState(FAILED); + return; + } + } + } +} + +void JingleChannel::OnStreamEvent(talk_base::StreamInterface* stream, + int events, int error) { + if (events & talk_base::SE_OPEN) { + SetState(OPEN); + } + + if (state_ == OPEN && (events & talk_base::SE_WRITE)) { + DoWrite(); + } + + if (state_ == OPEN && (events & talk_base::SE_READ)) { + DoRead(); + } + + if (events & talk_base::SE_CLOSE) { + SetState(CLOSED); + } +} + +void JingleChannel::SetState(State state) { + if (state == state_) + return; + state_ = state; + callback_->OnStateChange(this, state); +} + +void JingleChannel::Close() { + base::WaitableEvent event(true, false); + thread_->message_loop()->PostTask( + FROM_HERE, NewRunnableMethod(this, &JingleChannel::DoClose, &event)); + event.Wait(); +} + +void JingleChannel::DoClose(base::WaitableEvent* done_event) { + if (stream_.get()) + stream_->Close(); + SetState(CLOSED); + done_event->Signal(); +} + +size_t JingleChannel::write_buffer_size() { + AutoLock auto_lock(write_lock_); + return write_buffer_size_; +} + +} // namespace remoting diff --git a/remoting/jingle_glue/jingle_channel.h b/remoting/jingle_glue/jingle_channel.h new file mode 100644 index 0000000..e3f5e51 --- /dev/null +++ b/remoting/jingle_glue/jingle_channel.h @@ -0,0 +1,158 @@ +// 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_JINGLE_GLUE_JINGLE_CHANNEL_H_ +#define REMOTING_JINGLE_GLUE_JINGLE_CHANNEL_H_ + +#include <deque> +#include <string> + +#include "base/basictypes.h" +#include "base/condition_variable.h" +#include "base/lock.h" +#include "base/ref_counted.h" +#include "base/scoped_ptr.h" +#include "talk/base/sigslot.h" +#include "testing/gtest/include/gtest/gtest_prod.h" + +namespace base { +class WaitableEvent; +} // namespace base + +namespace talk_base { +class StreamInterface; +} // namespace talk_base + +namespace media { +class Buffer; +class DataBuffer; +} // namespace media + +namespace remoting { +class JingleThread; + +class JingleChannel : public base::RefCountedThreadSafe<JingleChannel> { + public: + enum State { + INITIALIZING, + CONNECTING, + OPEN, + CLOSED, + FAILED, + }; + + class Callback { + public: + virtual ~Callback() {} + + // Called when state of the connection is changed. + virtual void OnStateChange(JingleChannel* channel, State state) = 0; + + // Called when a new packet is received. + virtual void OnPacketReceived(JingleChannel* channel, + scoped_refptr<media::DataBuffer> data) = 0; + }; + + virtual ~JingleChannel(); + + // Puts data to the write buffer. + virtual void Write(scoped_refptr<media::DataBuffer> data); + + // Closes the tunnel. + virtual void Close(); + + // Current state of the tunnel. + State state() { return state_; } + + // JID of the other end of the channel. + const std::string& jid() { return jid_; } + + // Number of bytes currently stored in the write buffer. + size_t write_buffer_size(); + + protected: + // Needs access to constructor, Init(). + friend class JingleClient; + + // Constructor used by unit test only. + // TODO(hclam): Have to suppress warnnings in MSVC. + JingleChannel(); + + // Used by JingleClient to create an instance of the channel. |callback| + // must not be NULL. + JingleChannel(Callback* callback); + + // Initialized the channel. Ownership of the |stream| is transfered to + // caller. Ownership of |thread| is not. + void Init(JingleThread* thread, talk_base::StreamInterface* stream, + const std::string& jid); + void SetState(State state); + + JingleThread* thread_; + scoped_ptr<talk_base::StreamInterface> stream_; + State state_; + + private: + FRIEND_TEST(JingleChannelTest, Init); + FRIEND_TEST(JingleChannelTest, Write); + FRIEND_TEST(JingleChannelTest, Read); + FRIEND_TEST(JingleChannelTest, Close); + + typedef std::deque<scoped_refptr<media::DataBuffer> > DataQueue; + + // Event handler for the stream. It passes stream events from the stream + // to JingleChannel. + class EventHandler : public sigslot::has_slots<> { + protected: + EventHandler(JingleChannel* channel) + : channel_(channel) { } + + // Constructor used only by unit test. + EventHandler() : channel_(NULL) {} + + void OnStreamEvent(talk_base::StreamInterface* stream, + int events, int error) { + channel_->OnStreamEvent(stream, events, error); + } + friend class JingleChannel; + private: + JingleChannel* channel_; + }; + friend class EventHandler; + + // Event handler for the stream. + void OnStreamEvent(talk_base::StreamInterface* stream, + int events, int error); + + // Writes data from the buffer to the stream. Called + // from OnStreamEvent() in the jingle thread. + void DoWrite(); + + // Reads data from the stream and puts it to the read buffer. + // Called from OnStreamEvent() in the jingle thread. + void DoRead(); + + void DoClose(base::WaitableEvent* done_event); + + Callback* callback_; + EventHandler event_handler_; + std::string jid_; + + // Write buffer. |write_lock_| should be locked when accessing |write_queue_| + // and |write_buffer_size_|, but isn't neccessary for |current_write_buf_|. + // |current_write_buf_| is accessed only by the jingle thread. + // |write_buffer_size_| stores number of bytes currently in |write_queue_| + // and in |current_write_buf_|. + DataQueue write_queue_; + size_t write_buffer_size_; + Lock write_lock_; + scoped_refptr<media::DataBuffer> current_write_buf_; + size_t current_write_buf_pos_; + + DISALLOW_COPY_AND_ASSIGN(JingleChannel); +}; + +} // namespace remoting + +#endif // REMOTING_JINGLE_GLUE_JINGLE_CHANNEL_H_ diff --git a/remoting/jingle_glue/jingle_channel_unittest.cc b/remoting/jingle_glue/jingle_channel_unittest.cc new file mode 100644 index 0000000..1a6a01e --- /dev/null +++ b/remoting/jingle_glue/jingle_channel_unittest.cc @@ -0,0 +1,146 @@ +// 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/ref_counted.h" +#include "media/base/data_buffer.h" +#include "remoting/jingle_glue/jingle_channel.h" +#include "remoting/jingle_glue/jingle_thread.h" +#include "talk/base/stream.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using testing::_; +using testing::Return; +using testing::Mock; +using testing::SetArgumentPointee; + +namespace remoting { + +namespace { +const size_t kBufferSize = 100; +} // namespace + +class MockCallback : public JingleChannel::Callback { + public: + MOCK_METHOD2(OnStateChange, void(JingleChannel*, JingleChannel::State)); + MOCK_METHOD2(OnPacketReceived, void(JingleChannel*, + scoped_refptr<media::DataBuffer>)); +}; + +class MockStream : public talk_base::StreamInterface { + public: + virtual ~MockStream() {} + MOCK_CONST_METHOD0(GetState, talk_base::StreamState ()); + + MOCK_METHOD4(Read, talk_base::StreamResult (void*, size_t, + size_t*, int*)); + MOCK_METHOD4(Write, talk_base::StreamResult (const void*, size_t, + size_t*, int*)); + MOCK_CONST_METHOD1(GetAvailable, bool (size_t *)); + MOCK_METHOD0(Close, void ()); + + MOCK_METHOD3(PostEvent, void (talk_base::Thread*, int, int)); + MOCK_METHOD2(PostEvent, void (int, int)); +}; + +TEST(JingleChannelTest, Init) { + JingleThread thread; + + MockStream *stream = new MockStream(); + MockCallback callback; + + EXPECT_CALL(*stream, GetState()) + .Times(1) + .WillRepeatedly(Return(talk_base::SS_OPENING)); + + scoped_refptr<JingleChannel> channel = new JingleChannel(&callback); + + EXPECT_CALL(callback, OnStateChange(channel.get(), JingleChannel::CONNECTING)) + .Times(1); + + thread.Start(); + + EXPECT_EQ(JingleChannel::INITIALIZING, channel->state()); + channel->Init(&thread, stream, "user@domain.com"); + EXPECT_EQ(JingleChannel::CONNECTING, channel->state()); + channel->state_ = JingleChannel::CLOSED; + + thread.Stop(); +} + +TEST(JingleChannelTest, Write) { + JingleThread thread; + MockStream* stream = new MockStream(); // Freed by the channel. + MockCallback callback; + + scoped_refptr<media::DataBuffer> data = new media::DataBuffer(kBufferSize); + data->SetDataSize(kBufferSize); + + EXPECT_CALL(*stream, Write(static_cast<const void*>(data->GetData()), + kBufferSize, _, _)) + .WillOnce(DoAll(SetArgumentPointee<2>(kBufferSize), + Return(talk_base::SR_SUCCESS))); + + scoped_refptr<JingleChannel> channel = new JingleChannel(&callback); + + channel->thread_ = &thread; + channel->stream_.reset(stream); + channel->state_ = JingleChannel::OPEN; + thread.Start(); + channel->Write(data); + thread.Stop(); + channel->state_ = JingleChannel::CLOSED; +} + +TEST(JingleChannelTest, Read) { + JingleThread thread; + MockStream* stream = new MockStream(); // Freed by the channel. + MockCallback callback; + + scoped_refptr<media::DataBuffer> data = new media::DataBuffer(kBufferSize); + data->SetDataSize(kBufferSize); + + scoped_refptr<JingleChannel> channel = new JingleChannel(&callback); + + EXPECT_CALL(callback, OnPacketReceived(channel.get(), _)) + .Times(1); + + EXPECT_CALL(*stream, GetAvailable(_)) + .WillOnce(DoAll(SetArgumentPointee<0>(kBufferSize), + Return(true))) + .WillOnce(DoAll(SetArgumentPointee<0>(0), + Return(true))); + + EXPECT_CALL(*stream, Read(_, kBufferSize, _, _)) + .WillOnce(DoAll(SetArgumentPointee<2>(kBufferSize), + Return(talk_base::SR_SUCCESS))); + + channel->thread_ = &thread; + channel->stream_.reset(stream); + channel->state_ = JingleChannel::OPEN; + thread.Start(); + channel->OnStreamEvent(stream, talk_base::SE_READ, 0); + thread.Stop(); + channel->state_ = JingleChannel::CLOSED; +} + +TEST(JingleChannelTest, Close) { + JingleThread thread; + MockStream* stream = new MockStream(); // Freed by the channel. + MockCallback callback; + + EXPECT_CALL(*stream, Close()) + .Times(1); + + scoped_refptr<JingleChannel> channel = new JingleChannel(&callback); + + channel->thread_ = &thread; + channel->stream_.reset(stream); + channel->state_ = JingleChannel::OPEN; + thread.Start(); + channel->Close(); + thread.Stop(); +} + +} // namespace remoting diff --git a/remoting/jingle_glue/jingle_client.cc b/remoting/jingle_glue/jingle_client.cc new file mode 100644 index 0000000..66867f5 --- /dev/null +++ b/remoting/jingle_glue/jingle_client.cc @@ -0,0 +1,227 @@ +// 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/jingle_glue/jingle_client.h" + +#include "base/logging.h" +#include "base/waitable_event.h" +#include "base/message_loop.h" +#include "chrome/common/net/notifier/communicator/xmpp_socket_adapter.h" +#include "remoting/jingle_glue/jingle_thread.h" +#include "remoting/jingle_glue/relay_port_allocator.h" +#include "talk/base/asyncsocket.h" +#include "talk/base/ssladapter.h" +#include "talk/p2p/base/sessionmanager.h" +#include "talk/p2p/client/sessionmanagertask.h" +#ifdef USE_SSL_TUNNEL +#include "talk/session/tunnel/securetunnelsessionclient.h" +#endif +#include "talk/session/tunnel/tunnelsessionclient.h" + +namespace remoting { + +JingleClient::JingleClient() + : callback_(NULL), + state_(START) { } + +JingleClient::~JingleClient() { + // JingleClient can be destroyed only after it's closed. + DCHECK(state_ == CLOSED); +} + +void JingleClient::Init(const std::string& username, + const std::string& password, + Callback* callback) { + DCHECK(username != ""); + DCHECK(callback != NULL); + DCHECK(thread_ == NULL); // Init() can be called only once. + + callback_ = callback; + + username_ = username; + password_ = password; + + thread_.reset(new JingleThread()); + thread_->Start(); + thread_->message_loop()->PostTask( + FROM_HERE, NewRunnableMethod(this, &JingleClient::DoInitialize)); +} + +class JingleClient::ConnectRequest { + public: + ConnectRequest() + : completed_event_(true, false) { } + + JingleChannel* Wait() { + completed_event_.Wait(); + return channel_; + }; + + void Done(JingleChannel* channel) { + channel_ = channel; + completed_event_.Signal(); + }; + + private: + base::WaitableEvent completed_event_; + JingleChannel* channel_; +}; + +JingleChannel* JingleClient::Connect(const std::string& host_jid, + JingleChannel::Callback* callback) { + ConnectRequest request; + thread_->message_loop()->PostTask( + FROM_HERE, NewRunnableMethod(this, &JingleClient::DoConnect, + &request, host_jid, callback)); + return request.Wait(); +} + +void JingleClient::DoConnect(ConnectRequest* request, + const std::string& host_jid, + JingleChannel::Callback* callback) { + talk_base::StreamInterface* stream = + tunnel_session_client_->CreateTunnel(buzz::Jid(host_jid), ""); + DCHECK(stream != NULL); + + JingleChannel* channel = new JingleChannel(callback); + channel->Init(thread_.get(), stream, host_jid); + request->Done(channel); +} + +void JingleClient::Close() { + DCHECK(thread_ != NULL); // Close() be called only after Init(). + message_loop()->PostTask( + FROM_HERE, NewRunnableMethod(this, &JingleClient::DoClose)); + thread_->Stop(); + thread_.reset(NULL); +} + +void JingleClient::DoClose() { + client_->Disconnect(); + // Client is deleted by TaskRunner. + client_ = NULL; + tunnel_session_client_.reset(); + port_allocator_.reset(); + session_manager_.reset(); + network_manager_.reset(); + UpdateState(CLOSED); +} + +void JingleClient::DoInitialize() { + buzz::Jid login_jid(username_); + talk_base::InsecureCryptStringImpl password; + password.password() = password_; + + buzz::XmppClientSettings xcs; + xcs.set_user(login_jid.node()); + xcs.set_host(login_jid.domain()); + xcs.set_resource("chromoting"); + xcs.set_use_tls(true); + xcs.set_pass(talk_base::CryptString(password)); + xcs.set_server(talk_base::SocketAddress("talk.google.com", 5222)); + + client_ = new buzz::XmppClient(thread_->task_pump()); + client_->SignalStateChange.connect( + this, &JingleClient::OnConnectionStateChanged); + + buzz::AsyncSocket* socket = + new notifier::XmppSocketAdapter(xcs, false); + + client_->Connect(xcs, "", socket, NULL); + client_->Start(); + + network_manager_.reset(new talk_base::NetworkManager()); + + RelayPortAllocator* port_allocator = + new RelayPortAllocator(network_manager_.get(), "transp2"); + port_allocator_.reset(port_allocator); + port_allocator->SetJingleInfo(client_); + + session_manager_.reset(new cricket::SessionManager(port_allocator_.get())); +#ifdef USE_SSL_TUNNEL + cricket::SecureTunnelSessionClient* session_client = + new cricket::SecureTunnelSessionClient(client_->jid(), + session_manager_.get()); + if (!session_client->GenerateIdentity()) + return false; + tunnel_session_client_.reset(session_client); +#else // !USE_SSL_TUNNEL + tunnel_session_client_.reset( + new cricket::TunnelSessionClient(client_->jid(), + session_manager_.get())); +#endif // USE_SSL_TUNNEL + + receiver_ = new cricket::SessionManagerTask(client_, session_manager_.get()); + receiver_->EnableOutgoingMessages(); + receiver_->Start(); + + tunnel_session_client_->SignalIncomingTunnel.connect( + this, &JingleClient::OnIncomingTunnel); +} + +std::string JingleClient::GetFullJid() { + AutoLock auto_lock(full_jid_lock_); + return full_jid_; +} + +MessageLoop* JingleClient::message_loop() { + if (thread_ == NULL) { + return NULL; + } + return thread_->message_loop(); +} + +void JingleClient::OnConnectionStateChanged(buzz::XmppEngine::State state) { + switch (state) { + case buzz::XmppEngine::STATE_START: + UpdateState(START); + break; + case buzz::XmppEngine::STATE_OPENING: + UpdateState(CONNECTING); + break; + case buzz::XmppEngine::STATE_OPEN: + { + AutoLock auto_lock(full_jid_lock_); + full_jid_ = client_->jid().Str(); + } + UpdateState(CONNECTED); + break; + case buzz::XmppEngine::STATE_CLOSED: + UpdateState(CLOSED); + break; + } +} + +void JingleClient::OnIncomingTunnel( + cricket::TunnelSessionClient* client, buzz::Jid jid, + std::string description, cricket::Session* session) { + // Decline connection if we don't have callback. + if (!callback_) { + client->DeclineTunnel(session); + return; + } + + JingleChannel::Callback* channel_callback; + if (callback_->OnAcceptConnection(this, jid.Str(), &channel_callback)) { + DCHECK(channel_callback != NULL); + talk_base::StreamInterface* stream = + client->AcceptTunnel(session); + scoped_refptr<JingleChannel> channel(new JingleChannel(channel_callback)); + channel->Init(thread_.get(), stream, jid.Str()); + callback_->OnNewConnection(this, channel); + } else { + client->DeclineTunnel(session); + return; + } +} + +void JingleClient::UpdateState(State new_state) { + if (new_state != state_) { + state_ = new_state; + if (callback_) + callback_->OnStateChange(this, new_state); + } +} + +} // namespace remoting diff --git a/remoting/jingle_glue/jingle_client.h b/remoting/jingle_glue/jingle_client.h new file mode 100644 index 0000000..56d97e1 --- /dev/null +++ b/remoting/jingle_glue/jingle_client.h @@ -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. + +#ifndef REMOTING_JINGLE_GLUE_JINGLE_CLIENT_H_ +#define REMOTING_JINGLE_GLUE_JINGLE_CLIENT_H_ + +#include <string> + +#include "remoting/jingle_glue/jingle_channel.h" +#include "talk/xmpp/xmppclient.h" + +class MessageLoop; + +namespace talk_base { +class NetworkManager; +} // namespace talk_base + +namespace cricket { +class BasicPortAllocator; +class SessionManager; +class TunnelSessionClient; +class SessionManagerTask; +class Session; +} // namespace cricket + +namespace remoting { + +class JingleClient : public base::RefCountedThreadSafe<JingleClient>, + public sigslot::has_slots<> { + public: + enum State { + START, // Initial state. + CONNECTING, + CONNECTED, + CLOSED, + }; + + class Callback { + public: + virtual ~Callback() {} + + // Called when state of the connection is changed. + virtual void OnStateChange(JingleClient* client, State state) = 0; + + // Called when a client attempts to connect to the machine. If the + // connection should be accepted, must return true and must set + // channel_callback to the callback for the new channel. + virtual bool OnAcceptConnection( + JingleClient* client, const std::string& jid, + JingleChannel::Callback** channel_callback) = 0; + + // Called when a new client connects to the host. Ownership of the |channel| + // is transfered to the callee. + virtual void OnNewConnection(JingleClient* client, + scoped_refptr<JingleChannel> channel) = 0; + }; + + JingleClient(); + virtual ~JingleClient(); + + // Starts jingle thread and XMPP connection inialization. Must be called + // only once. message_loop() is guaranteed to exist after this method returns, + // but the connection may not be open yet. |callback| specifies callback + // object for the client and must not be NULL. + // TODO(sergeyu): Replace password with a token. + void Init(const std::string& username, const std::string& password, + Callback* callback); + + // Creates new JingleChannel connected to the host with the specified jid. + // The result is returned immediately but the channel fails if the host + // rejects connection. |host_jid| must be a full jid (includes resource ID). + // Ownership of the result is transfered to the caller. The channel must + // be closed/destroyed before JingleClient is destroyed. + JingleChannel* Connect(const std::string& host_jid, + JingleChannel::Callback* callback); + + // Closes XMPP connection and stops the thread. Must be called before the + // object is destroyed. + void Close(); + + // Returns JID with resource ID. Empty string is returned if full JID is not + // known yet, i.e. authentication hasn't finished. + std::string GetFullJid(); + + // Current state of the client. + State state() { return state_; } + + // Returns XmppClient object for the xmpp connection or NULL if not connected. + buzz::XmppClient* xmpp_client() { return client_; } + + // Message loop for the jingle thread or NULL if the thread is not started. + MessageLoop* message_loop(); + + private: + // Used by Connect(). + class ConnectRequest; + + void OnConnectionStateChanged(buzz::XmppEngine::State state); + + void OnIncomingTunnel(cricket::TunnelSessionClient* client, buzz::Jid jid, + std::string description, cricket::Session* session); + + void DoInitialize(); + + // Used by Connect(). + void DoConnect(ConnectRequest* request, + const std::string& host_jid, + JingleChannel::Callback* callback); + + // Used by Close(). + void DoClose(); + + // Updates current state of the connection. Must be called only in + // the jingle thread. + void UpdateState(State new_state); + + buzz::XmppClient* client_; + scoped_ptr<JingleThread> thread_; + State state_; + Callback* callback_; + + std::string username_; + std::string password_; + Lock full_jid_lock_; + std::string full_jid_; + + scoped_ptr<talk_base::NetworkManager> network_manager_; + scoped_ptr<cricket::BasicPortAllocator> port_allocator_; + scoped_ptr<cricket::SessionManager> session_manager_; + scoped_ptr<cricket::TunnelSessionClient> tunnel_session_client_; + cricket::SessionManagerTask* receiver_; + + DISALLOW_COPY_AND_ASSIGN(JingleClient); +}; + +} // namespace remoting + +#endif // REMOTING_JINGLE_GLUE_JINGLE_CLIENT_H_ diff --git a/remoting/jingle_glue/jingle_info_task.cc b/remoting/jingle_glue/jingle_info_task.cc new file mode 100644 index 0000000..c2e6525 --- /dev/null +++ b/remoting/jingle_glue/jingle_info_task.cc @@ -0,0 +1,132 @@ +// 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/jingle_glue/jingle_info_task.h" + +#include "base/scoped_ptr.h" +#include "talk/base/socketaddress.h" +#include "talk/xmpp/constants.h" +#include "talk/xmpp/xmppclient.h" + +namespace remoting { + +// This code is a copy of googleclient/talk/app/jingleinfotask.cc . + +class JingleInfoTask::JingleInfoGetTask : public XmppTask { + public: + explicit JingleInfoGetTask(talk_base::TaskParent* parent) : + XmppTask(parent, buzz::XmppEngine::HL_SINGLE), + done_(false) { } + + virtual int ProcessStart() { + // Set jingle info query IQ stanza. + scoped_ptr<buzz::XmlElement> get_iq( + MakeIq(buzz::STR_GET, buzz::JID_EMPTY, task_id())); + get_iq->AddElement(new buzz::XmlElement(buzz::QN_JINGLE_INFO_QUERY, true)); + if (SendStanza(get_iq.get()) != buzz::XMPP_RETURN_OK) { + return STATE_ERROR; + } + return STATE_RESPONSE; + } + + virtual int ProcessResponse() { + if (done_) { + return STATE_DONE; + } + return STATE_BLOCKED; + } + + protected: + virtual bool HandleStanza(const buzz::XmlElement* stanza) { + if (!MatchResponseIq(stanza, buzz::JID_EMPTY, task_id())) { + return false; + } + + if (stanza->Attr(buzz::QN_TYPE) != buzz::STR_RESULT) { + return false; + } + + // Queue the stanza with the parent so these don't get handled out of order. + JingleInfoTask* parent = static_cast<JingleInfoTask*>(GetParent()); + parent->QueueStanza(stanza); + + // Wake ourselves so we can go into the done state. + done_ = true; + Wake(); + return true; + } + + bool done_; +}; + + +void JingleInfoTask::RefreshJingleInfoNow() { + JingleInfoGetTask* get_task = new JingleInfoGetTask(this); + get_task->Start(); +} + +bool JingleInfoTask::HandleStanza(const buzz::XmlElement* stanza) { + if (!MatchRequestIq(stanza, "set", buzz::QN_JINGLE_INFO_QUERY)) { + return false; + } + + // Only respect relay push from the server. + buzz::Jid from(stanza->Attr(buzz::QN_FROM)); + if (from != buzz::JID_EMPTY && + !from.BareEquals(GetClient()->jid()) && + from != buzz::Jid(GetClient()->jid().domain())) { + return false; + } + + QueueStanza(stanza); + return true; +} + +int JingleInfoTask::ProcessStart() { + std::vector<std::string> relay_hosts; + std::vector<talk_base::SocketAddress> stun_hosts; + std::string relay_token; + const buzz::XmlElement* stanza = NextStanza(); + if (stanza == NULL) { + return STATE_BLOCKED; + } + const buzz::XmlElement* query = + stanza->FirstNamed(buzz::QN_JINGLE_INFO_QUERY); + if (query == NULL) { + return STATE_START; + } + const buzz::XmlElement* stun = query->FirstNamed(buzz::QN_JINGLE_INFO_STUN); + if (stun) { + for (const buzz::XmlElement* server = + stun->FirstNamed(buzz::QN_JINGLE_INFO_SERVER); + server != NULL; + server = server->NextNamed(buzz::QN_JINGLE_INFO_SERVER)) { + std::string host = server->Attr(buzz::QN_JINGLE_INFO_HOST); + std::string port = server->Attr(buzz::QN_JINGLE_INFO_UDP); + if (host != buzz::STR_EMPTY && host != buzz::STR_EMPTY) { + // TODO(sergeyu): Avoid atoi() here. + stun_hosts.push_back( + talk_base::SocketAddress(host, atoi(port.c_str()))); + } + } + } + + const buzz::XmlElement* relay = query->FirstNamed(buzz::QN_JINGLE_INFO_RELAY); + if (relay) { + relay_token = relay->TextNamed(buzz::QN_JINGLE_INFO_TOKEN); + for (const buzz::XmlElement* server = + relay->FirstNamed(buzz::QN_JINGLE_INFO_SERVER); + server != NULL; + server = server->NextNamed(buzz::QN_JINGLE_INFO_SERVER)) { + std::string host = server->Attr(buzz::QN_JINGLE_INFO_HOST); + if (host != buzz::STR_EMPTY) { + relay_hosts.push_back(host); + } + } + } + SignalJingleInfo(relay_token, relay_hosts, stun_hosts); + return STATE_START; +} + +} // namespace remoting diff --git a/remoting/jingle_glue/jingle_info_task.h b/remoting/jingle_glue/jingle_info_task.h new file mode 100644 index 0000000..f02e54e --- /dev/null +++ b/remoting/jingle_glue/jingle_info_task.h @@ -0,0 +1,45 @@ +// 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_JINGLE_GLUE_JINGLE_INFO_TASK_H_ +#define REMOTING_JINGLE_GLUE_JINGLE_INFO_TASK_H_ + +#include <vector> +#include <string> + +#include "talk/base/sigslot.h" +#include "talk/p2p/client/httpportallocator.h" +#include "talk/xmpp/xmppengine.h" +#include "talk/xmpp/xmpptask.h" + +namespace remoting { + +// JingleInfoTask is used to discover addresses of jingle servers. +// See http://code.google.com/apis/talk/jep_extensions/jingleinfo.html +// for more details about the protocol. +// +// This is a copy of googleclient/talk/app/jingleinfotask.h . +class JingleInfoTask : public buzz::XmppTask { + public: + explicit JingleInfoTask(talk_base::TaskParent* parent) + : XmppTask(parent, buzz::XmppEngine::HL_TYPE) {} + + virtual int ProcessStart(); + void RefreshJingleInfoNow(); + + sigslot::signal3<const std::string&, + const std::vector<std::string>&, + const std::vector<talk_base::SocketAddress>&> + SignalJingleInfo; + + protected: + class JingleInfoGetTask; + friend class JingleInfoGetTask; + + virtual bool HandleStanza(const buzz::XmlElement* stanza); +}; + +} // namespace remoting + +#endif // REMOTING_JINGLE_GLUE_JINGLE_INFO_TASK_H_ diff --git a/remoting/jingle_glue/jingle_test_client.cc b/remoting/jingle_glue/jingle_test_client.cc new file mode 100644 index 0000000..38677fe --- /dev/null +++ b/remoting/jingle_glue/jingle_test_client.cc @@ -0,0 +1,159 @@ +// 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 "build/build_config.h" + +#if !defined(OS_WIN) +extern "C" { +#include <unistd.h> +} +#endif // !defined(OS_WIN) + +#include <iostream> +#include <list> + +#include "base/at_exit.h" +#include "media/base/data_buffer.h" +#include "remoting/jingle_glue/jingle_channel.h" +#include "remoting/jingle_glue/jingle_client.h" + +using remoting::JingleClient; +using remoting::JingleChannel; + +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); +#else // defined(OS_WIN) + if (on) + system("stty echo"); + else + system("stty -echo"); +#endif // !defined(OS_WIN) +} + +class JingleTestClient : public JingleChannel::Callback, + public JingleClient::Callback { + public: + virtual ~JingleTestClient() {} + + void Run(const std::string& username, const std::string& password, + const std::string& host_jid) { + client_ = new JingleClient(); + client_->Init(username, password, this); + + if (host_jid != "") { + scoped_refptr<JingleChannel> channel = client_->Connect(host_jid, this); + channels_.push_back(channel); + } + + while (true) { + std::string line; + std::getline(std::cin, line); + + { + AutoLock auto_lock(channels_lock_); + + // Broadcast message to all clients. + for (ChannelsList::iterator it = channels_.begin(); + it != channels_.end(); ++it) { + uint8* buf = new uint8[line.length()]; + memcpy(buf, line.c_str(), line.length()); + (*it)->Write(new media::DataBuffer(buf, line.length())); + } + } + + if (line == "exit") + break; + } + + while (!channels_.empty()) { + channels_.front()->Close(); + channels_.pop_front(); + } + + client_->Close(); + } + + // JingleChannel::Callback interface. + void OnStateChange(JingleChannel* channel, JingleChannel::State state) { + LOG(INFO) << "State of " << channel->jid() << " changed to " << state; + } + + void OnPacketReceived(JingleChannel* channel, + scoped_refptr<media::DataBuffer> buffer) { + std::string str(reinterpret_cast<const char*>(buffer->GetData()), + buffer->GetDataSize()); + std::cout << "(" << channel->jid() << "): " << str << std::endl; + } + + // JingleClient::Callback interface. + void OnStateChange(JingleClient* client, JingleClient::State state) { + if (state == JingleClient::CONNECTED) { + std::cerr << "Connected as " << client->GetFullJid() << std::endl; + } else if (state == JingleClient::CLOSED) { + std::cerr << "Connection closed" << std::endl; + } + } + + bool OnAcceptConnection(JingleClient* client, const std::string& jid, + JingleChannel::Callback** callback) { + std::cerr << "Accepting new connection from " << jid << std::endl; + *callback = this; + return true; + } + + void OnNewConnection(JingleClient* client, + scoped_refptr<JingleChannel> channel) { + std::cerr << "Connected to " << channel->jid() << std::endl; + AutoLock auto_lock(channels_lock_); + channels_.push_back(channel); + } + + private: + typedef std::list<scoped_refptr<JingleChannel> > ChannelsList; + + scoped_refptr<JingleClient> client_; + ChannelsList channels_; + Lock channels_lock_; +}; + +int main(int argc, char** argv) { + if (argc > 2) + std::cerr << "Usage: " << argv[0] << " [<host_jid>]" << std::endl; + + base::AtExitManager exit_manager; + + std::string host_jid = argc == 2 ? argv[1] : ""; + + 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; + + JingleTestClient client; + + client.Run(username, password, host_jid); + + return 0; +} diff --git a/remoting/jingle_glue/jingle_thread.cc b/remoting/jingle_glue/jingle_thread.cc new file mode 100644 index 0000000..5090251 --- /dev/null +++ b/remoting/jingle_glue/jingle_thread.cc @@ -0,0 +1,80 @@ +// 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/jingle_glue/jingle_thread.h" + +#include "base/logging.h" +#include "base/message_loop.h" +#include "talk/base/ssladapter.h" + +namespace remoting { + +const int kRunTasksMessageId = 1; + +TaskPump::TaskPump() { +} + +void TaskPump::WakeTasks() { + talk_base::Thread::Current()->Post(this); +} + +int64 TaskPump::CurrentTime() { + return static_cast<int64>(talk_base::Time()); +} + +void TaskPump::OnMessage(talk_base::Message* pmsg) { + RunTasks(); +} + +JingleThread::JingleThread() + : message_loop_(NULL), + task_pump_(NULL), + started_event_(true, false) { } + +JingleThread::~JingleThread() { } + +void JingleThread::Start() { + Thread::Start(); + started_event_.Wait(); +} + +void JingleThread::Run() { + LOG(INFO) << "Started Jingle thread."; + + MessageLoopForIO message_loop; + message_loop_ = &message_loop; + + TaskPump task_pump; + task_pump_ = &task_pump; + + // Signal after we've initialized |message_loop_| and |task_pump_|. + started_event_.Signal(); + + Post(this, kRunTasksMessageId); + + Thread::Run(); + + message_loop.RunAllPending(); + + task_pump_ = NULL; + message_loop_ = NULL; + + LOG(INFO) << "Jingle thread finished."; +} + +// This method is called every 20ms to process tasks from |message_loop_| +// on this thread. +// TODO(sergeyu): Remove it when JingleThread moved to Chromium's base::Thread. +void JingleThread::PumpAuxiliaryLoops() { + MessageLoop::current()->RunAllPending(); + + // Schedule next execution 20ms from now. + PostDelayed(20, this, kRunTasksMessageId); +} + +void JingleThread::OnMessage(talk_base::Message* msg) { + PumpAuxiliaryLoops(); +} + +} // namespace remoting diff --git a/remoting/jingle_glue/jingle_thread.h b/remoting/jingle_glue/jingle_thread.h new file mode 100644 index 0000000..ecef1fb --- /dev/null +++ b/remoting/jingle_glue/jingle_thread.h @@ -0,0 +1,70 @@ +// 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_JINGLE_GLUE_JINGLE_THREAD_H +#define REMOTING_JINGLE_GLUE_JINGLE_THREAD_H + +#include "base/tracked_objects.h" +#include "base/waitable_event.h" +#include "talk/base/messagequeue.h" +#include "talk/base/taskrunner.h" +#include "talk/base/thread.h" + +class MessageLoop; + +namespace buzz { +class XmppClient; +} + +namespace remoting { + +class TaskPump : public talk_base::MessageHandler, + public talk_base::TaskRunner { + public: + TaskPump(); + + // TaskRunner methods. + void WakeTasks(); + int64 CurrentTime(); + + // MessageHandler methods. + void OnMessage(talk_base::Message* pmsg); +}; + +// TODO(sergeyu): This class should be changed to inherit from Chromiums +// base::Thread instead of libjingle's thread. +class JingleThread : public talk_base::Thread, + private talk_base::MessageHandler { + public: + JingleThread(); + virtual ~JingleThread(); + + void Start(); + + // Main function for the thread. Should not be called directly. + void Run(); + + // Returns Chromiums message loop for this thread. + // TODO(sergeyu): remove this methid when we use base::Thread insted of + // talk_base::Thread + MessageLoop* message_loop() { return message_loop_; } + + // Returns task pump if the thread is running, otherwise NULL is returned. + TaskPump* task_pump() { return task_pump_; } + + private: + virtual void OnMessage(talk_base::Message* msg); + + void PumpAuxiliaryLoops(); + + TaskPump* task_pump_; + base::WaitableEvent started_event_; + MessageLoop* message_loop_; + + DISALLOW_COPY_AND_ASSIGN(JingleThread); +}; + +} // namespace remoting + +#endif // REMOTING_JINGLE_GLUE_JINGLE_THREAD_H diff --git a/remoting/jingle_glue/jingle_thread_unittest.cc b/remoting/jingle_glue/jingle_thread_unittest.cc new file mode 100644 index 0000000..a29b69c --- /dev/null +++ b/remoting/jingle_glue/jingle_thread_unittest.cc @@ -0,0 +1,27 @@ +// 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 "remoting/jingle_glue/jingle_thread.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace remoting { + +class MockTask : public Task { + public: + MOCK_METHOD0(Run, void()); +}; + +TEST(JingleThreadTest, PostTask) { + JingleThread thread; + MockTask* task = new MockTask(); + EXPECT_CALL(*task, Run()); + + thread.Start(); + thread.message_loop()->PostTask(FROM_HERE, task); + thread.Stop(); +} + +} // namespace remoting diff --git a/remoting/jingle_glue/mock_objects.h b/remoting/jingle_glue/mock_objects.h new file mode 100644 index 0000000..c1a1e26 --- /dev/null +++ b/remoting/jingle_glue/mock_objects.h @@ -0,0 +1,26 @@ +// 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_JINGLE_GLUE_MOCK_OBJECTS_H_ +#define REMOTING_JINGLE_GLUE_MOCK_OBJECTS_H_ + +#include "remoting/jingle_glue/jingle_channel.h" +#include "testing/gmock/include/gmock/gmock.h" + +namespace remoting { + +class MockJingleChannel : public JingleChannel { + public: + MockJingleChannel() {} + + MOCK_METHOD1(Write, void(scoped_refptr<media::DataBuffer> data)); + MOCK_METHOD0(Close, void()); + + private: + DISALLOW_COPY_AND_ASSIGN(MockJingleChannel); +}; + +} // namespace remoting + +#endif // REMOTING_JINGLE_GLUE_MOCK_OBJECTS_H_ diff --git a/remoting/jingle_glue/relay_port_allocator.cc b/remoting/jingle_glue/relay_port_allocator.cc new file mode 100644 index 0000000..8b50dca --- /dev/null +++ b/remoting/jingle_glue/relay_port_allocator.cc @@ -0,0 +1,29 @@ +// 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/jingle_glue/relay_port_allocator.h" + +#include "remoting/jingle_glue/jingle_info_task.h" +#include "talk/xmpp/xmppclient.h" + +namespace remoting { + +void RelayPortAllocator::OnJingleInfo( + const std::string & token, + const std::vector<std::string> & relay_hosts, + const std::vector<talk_base::SocketAddress> & stun_hosts) { + this->SetRelayToken(token); + this->SetStunHosts(stun_hosts); + this->SetRelayHosts(relay_hosts); +} + +void RelayPortAllocator::SetJingleInfo(buzz::XmppClient* client) { + // The JingleInfoTask is freed by the task-runner. + JingleInfoTask* jit = new JingleInfoTask(client); + jit->SignalJingleInfo.connect(this, &RelayPortAllocator::OnJingleInfo); + jit->Start(); + jit->RefreshJingleInfoNow(); +} + +} // namespace remoting diff --git a/remoting/jingle_glue/relay_port_allocator.h b/remoting/jingle_glue/relay_port_allocator.h new file mode 100644 index 0000000..2651d3f3 --- /dev/null +++ b/remoting/jingle_glue/relay_port_allocator.h @@ -0,0 +1,38 @@ +// 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_JINGLE_GLUE_RELAY_PORT_ALLOCATOR_H_ +#define REMOTING_JINGLE_GLUE_RELAY_PORT_ALLOCATOR_H_ + +#include <string> + +#include "talk/base/sigslot.h" +#include "talk/p2p/client/httpportallocator.h" + +namespace buzz { +class XmppClient; +} // namespace buzz + +namespace remoting { + +class RelayPortAllocator: public cricket::HttpPortAllocator, + public sigslot::has_slots<> { + public: + RelayPortAllocator(talk_base::NetworkManager* network_manager, + const std::string& user_agent): + cricket::HttpPortAllocator(network_manager, user_agent) { } + + void OnJingleInfo(const std::string& token, + const std::vector<std::string>& relay_hosts, + const std::vector<talk_base::SocketAddress>& stun_hosts); + + void SetJingleInfo(buzz::XmppClient* client); + + private: + DISALLOW_COPY_AND_ASSIGN(RelayPortAllocator); +}; + +} // namespace remoting + +#endif // REMOTING_JINGLE_GLUE_RELAY_PORT_ALLOCATOR_H_ diff --git a/remoting/run_all_unittests.cc b/remoting/run_all_unittests.cc new file mode 100644 index 0000000..f008d3e --- /dev/null +++ b/remoting/run_all_unittests.cc @@ -0,0 +1,9 @@ +// 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/test/test_suite.h" + +int main(int argc, char** argv) { + return TestSuite(argc, argv).Run(); +} diff --git a/remoting/tools/client_webserver/main.c b/remoting/tools/client_webserver/main.c new file mode 100644 index 0000000..1182ab0 --- /dev/null +++ b/remoting/tools/client_webserver/main.c @@ -0,0 +1,164 @@ +// 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. + +// Simple webserver that returns empty content with the requested mimetype. +// +// For example: +// http://localhost:8080/pepper-application/x-chromoting-plugin +// Will return empty content, but with the requested mimetype: +// Content-Type: pepper-application/x-chromoting-plugin +// +// This is useful for testing the Chromoting plugin while we wait for +// updated mimetype support to be added to Chrome. + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/wait.h> +#include <netinet/in.h> + +#define PORT 8080 + +void error(const char *msg) { + fprintf(stderr, "ERROR - %s\n", msg); + exit(1); +} + +// Read text data from a socket to a buffer. +// Data up to the next \n char is read into the buffer. +void read_text_data(int sock, char *buffer, int buffsize) { + int num_bytes; + *buffer = '\0'; + + for (num_bytes = 1; num_bytes < buffsize-1; num_bytes++) { + char ch; + + int num_bytes_read = read(sock, &ch, 1); + if (num_bytes_read == 1) { + if (ch == '\n') { + break; + } + *buffer++ = ch; + } else if (num_bytes_read == 0) { + break; + } else { + error("read_text_data failed"); + } + } + *buffer++ = '\0'; +} + +// Write data from a null-terminated buffer to a socket. +void write_data(int sock, const char *buffer) { + int num_bytes = strlen(buffer); + + while (num_bytes > 0) { + int num_bytes_written = write(sock, buffer, num_bytes); + if (num_bytes_written <= 0) { + error("write_data failed"); + } + num_bytes -= num_bytes_written; + buffer += num_bytes_written; + } +} + +void handle_request(int connection) { + printf("Handling request...\n"); + char buffer[512]; + buffer[0] = '\0'; + + // Read the first line of the request. This will be something like: + // GET /index.html HTTP/1.1 + read_text_data(connection, buffer, 512); + + char *saveptr; + char *request = strtok_r(buffer, " ", &saveptr); + char *resource = strtok_r(NULL, " ", &saveptr); + char *version = strtok_r(NULL, " ", &saveptr); + + char mime_type[512]; + strncpy(mime_type, &resource[1], 511); + mime_type[511] = '\0'; + + if (strcmp(request, "GET")) { + printf("Unknown request: 'GET %s %s'\n", request, version); + } else { + printf("Requesting '%s'\n", mime_type); + } + + // Keep reading until we encounter a blank line. + // This will skip over the Host, Connection, User-Agent, ... + char ignore[512] = "ignore"; + while (strspn(ignore, " \n\r\t") != strlen(ignore)) { + read_text_data(connection, ignore, sizeof(ignore)); + } + + // At this point, a normal webserver would verify that the requested + // resource exists and then return it with the appropriate mimetype. + + // However, we return empty content, but with the requested plugin mimetype. + write_data(connection, "HTTP/1.0 200 OK\r\n"); + write_data(connection, "Content-Type: "); + write_data(connection, mime_type); + write_data(connection, "\r\n\r\n"); + + // This dummy data is unused, but must be present or else the reader may hang. + write_data(connection, "Data\n"); +} + +int main(int argc, char *argv[]) { + printf("Chromoting client webserver: http://localhost:%d\n", PORT); + + signal(SIGCHLD, SIG_IGN); + + // Create socket. + int sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock < 0) { + error("Unable to open socket"); + } + + // Initialize socket address struct. + struct sockaddr_in serv_addr; + bzero((char*)&serv_addr, sizeof(serv_addr)); + serv_addr.sin_family = AF_INET; + serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); + serv_addr.sin_port = htons(PORT); + + // Bind socket. + if (bind(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) { + error("Unable to bind socket"); + } + + if (listen(sock, 5) < 0) { + error("Unable to listen to socket"); + } + + while (1) { + int connection = accept(sock, NULL, NULL); + if (connection < 0) { + error("Unable to accept connection"); + } + + pid_t pid = fork(); + if (pid == 0) { + // Child process. + if (close(sock) < 0) { + error("Unable to close socket in child"); + } + + // Handle the request. + handle_request(connection); + + // Success. Exit to kill child process. + exit(0); + } else { + // Parent process. + if (close(connection) < 0) { + error("Unable to close connection in parent"); + } + } + } + + return 0; +} |