summaryrefslogtreecommitdiffstats
path: root/remoting
diff options
context:
space:
mode:
authorgarykac@google.com <garykac@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2010-06-07 19:58:23 +0000
committergarykac@google.com <garykac@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2010-06-07 19:58:23 +0000
commitcb3b1f93130040a150e7fcc57cd4d5a75569685a (patch)
treeff93665e3c1478c61663d1107cd42dc25b31448d /remoting
parentb0110e822ac2b2db56d1b1542aad06da573cd544 (diff)
downloadchromium_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')
-rw-r--r--remoting/WATCHLISTS18
-rw-r--r--remoting/base/constants.cc11
-rw-r--r--remoting/base/constants.h16
-rw-r--r--remoting/base/mock_objects.h30
-rw-r--r--remoting/base/multiple_array_input_stream.cc96
-rw-r--r--remoting/base/multiple_array_input_stream.h49
-rw-r--r--remoting/base/multiple_array_input_stream_unittest.cc94
-rw-r--r--remoting/base/protocol/chromotocol.gyp79
-rw-r--r--remoting/base/protocol/chromotocol.proto153
-rw-r--r--remoting/base/protocol_decoder.cc149
-rw-r--r--remoting/base/protocol_decoder.h72
-rw-r--r--remoting/base/protocol_decoder_unittest.cc113
-rw-r--r--remoting/base/protocol_util.cc27
-rw-r--r--remoting/base/protocol_util.h24
-rw-r--r--remoting/chromoting.gyp362
-rw-r--r--remoting/client/decoder.h92
-rw-r--r--remoting/client/decoder_verbatim.cc67
-rw-r--r--remoting/client/decoder_verbatim.h33
-rw-r--r--remoting/client/decoder_verbatim_unittest.cc13
-rw-r--r--remoting/client/host_connection.cc78
-rw-r--r--remoting/client/host_connection.h77
-rw-r--r--remoting/client/pepper/fake_browser.cc157
-rw-r--r--remoting/client/pepper/fake_browser.h61
-rw-r--r--remoting/client/pepper/pepper_main.cc373
-rw-r--r--remoting/client/pepper/pepper_plugin.cc22
-rw-r--r--remoting/client/pepper/pepper_plugin.h106
-rw-r--r--remoting/client/plugin/chromoting_main.cc24
-rw-r--r--remoting/client/plugin/chromoting_plugin.cc114
-rw-r--r--remoting/client/plugin/chromoting_plugin.h53
-rw-r--r--remoting/client/plugin/chromoting_plugin_test.html9
-rw-r--r--remoting/client/plugin/chromoting_plugin_unittest.cc93
-rw-r--r--remoting/client/plugin/chromotocol.h79
-rw-r--r--remoting/client/plugin/client.cc392
-rw-r--r--remoting/client/plugin/client.h66
-rw-r--r--remoting/client/plugin/compression.cc114
-rw-r--r--remoting/client/plugin/compression.h90
-rw-r--r--remoting/client/plugin/decoder.h39
-rw-r--r--remoting/client/plugin/host_connection.cc89
-rw-r--r--remoting/client/plugin/host_connection.h37
-rw-r--r--remoting/client/simple_client.cc196
-rw-r--r--remoting/host/capturer.cc49
-rw-r--r--remoting/host/capturer.h123
-rw-r--r--remoting/host/capturer_fake.cc86
-rw-r--r--remoting/host/capturer_fake.h44
-rw-r--r--remoting/host/capturer_fake_ascii.cc88
-rw-r--r--remoting/host/capturer_fake_ascii.h41
-rw-r--r--remoting/host/capturer_gdi.cc128
-rw-r--r--remoting/host/capturer_gdi.h52
-rw-r--r--remoting/host/capturer_gdi_unittest.cc13
-rw-r--r--remoting/host/capturer_linux.cc54
-rw-r--r--remoting/host/capturer_linux.h33
-rw-r--r--remoting/host/capturer_linux_unittest.cc13
-rw-r--r--remoting/host/capturer_mac.cc54
-rw-r--r--remoting/host/capturer_mac.h33
-rw-r--r--remoting/host/capturer_mac_unittest.cc13
-rw-r--r--remoting/host/client_connection.cc173
-rw-r--r--remoting/host/client_connection.h141
-rw-r--r--remoting/host/client_connection_unittest.cc99
-rw-r--r--remoting/host/differ_block.cc28
-rw-r--r--remoting/host/differ_block.h23
-rw-r--r--remoting/host/differ_block_unittest.cc46
-rw-r--r--remoting/host/encoder.h63
-rw-r--r--remoting/host/encoder_verbatim.cc97
-rw-r--r--remoting/host/encoder_verbatim.h47
-rw-r--r--remoting/host/encoder_vp8.cc133
-rw-r--r--remoting/host/encoder_vp8.h61
-rw-r--r--remoting/host/encoder_vp8_unittest.cc69
-rw-r--r--remoting/host/event_executor.h34
-rw-r--r--remoting/host/event_executor_linux.cc18
-rw-r--r--remoting/host/event_executor_linux.h28
-rw-r--r--remoting/host/event_executor_mac.cc18
-rw-r--r--remoting/host/event_executor_mac.h28
-rw-r--r--remoting/host/event_executor_win.cc405
-rw-r--r--remoting/host/event_executor_win.h28
-rw-r--r--remoting/host/heartbeat_sender.cc69
-rw-r--r--remoting/host/heartbeat_sender.h41
-rw-r--r--remoting/host/mock_objects.h103
-rw-r--r--remoting/host/session_manager.cc401
-rw-r--r--remoting/host/session_manager.h190
-rw-r--r--remoting/host/session_manager_unittest.cc139
-rw-r--r--remoting/host/simple_host.cc201
-rw-r--r--remoting/host/simple_host.h131
-rw-r--r--remoting/host/simple_host_process.cc118
-rw-r--r--remoting/jingle_glue/iq_request.cc79
-rw-r--r--remoting/jingle_glue/iq_request.h65
-rw-r--r--remoting/jingle_glue/iq_request_unittest.cc40
-rw-r--r--remoting/jingle_glue/jingle_channel.cc210
-rw-r--r--remoting/jingle_glue/jingle_channel.h158
-rw-r--r--remoting/jingle_glue/jingle_channel_unittest.cc146
-rw-r--r--remoting/jingle_glue/jingle_client.cc227
-rw-r--r--remoting/jingle_glue/jingle_client.h139
-rw-r--r--remoting/jingle_glue/jingle_info_task.cc132
-rw-r--r--remoting/jingle_glue/jingle_info_task.h45
-rw-r--r--remoting/jingle_glue/jingle_test_client.cc159
-rw-r--r--remoting/jingle_glue/jingle_thread.cc80
-rw-r--r--remoting/jingle_glue/jingle_thread.h70
-rw-r--r--remoting/jingle_glue/jingle_thread_unittest.cc27
-rw-r--r--remoting/jingle_glue/mock_objects.h26
-rw-r--r--remoting/jingle_glue/relay_port_allocator.cc29
-rw-r--r--remoting/jingle_glue/relay_port_allocator.h38
-rw-r--r--remoting/run_all_unittests.cc9
-rw-r--r--remoting/tools/client_webserver/main.c164
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;
+}