diff options
author | hclam@chromium.org <hclam@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-06-09 21:56:39 +0000 |
---|---|---|
committer | hclam@chromium.org <hclam@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-06-09 21:56:39 +0000 |
commit | ef0a59a6fd722a0910196c951dc676c19127a28b (patch) | |
tree | 9d90ab9227e4222fa312dadae114c8d2247028bf /remoting/client | |
parent | e9fdd159ffd94e3e097bd6905d84e6b564b04c2c (diff) | |
download | chromium_src-ef0a59a6fd722a0910196c951dc676c19127a28b.zip chromium_src-ef0a59a6fd722a0910196c951dc676c19127a28b.tar.gz chromium_src-ef0a59a6fd722a0910196c951dc676c19127a28b.tar.bz2 |
Implement a chromoting client using X11
Using XRender to render the chromoting client. This patch has done several things:
1. Rename chromotocol_pb to remoting
2. Defined ChromotingView as the display area of the remote view
3. Implemented X11Client as the client that uses X11 for display
4. Implemented X11View that uses XRender for drawing
5. Fixed several problems in host capturer and encoder
Review URL: http://codereview.chromium.org/2745006
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@49329 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'remoting/client')
-rw-r--r-- | remoting/client/chromoting_view.h | 37 | ||||
-rw-r--r-- | remoting/client/client_util.cc | 76 | ||||
-rw-r--r-- | remoting/client/client_util.h | 19 | ||||
-rw-r--r-- | remoting/client/decoder.h | 43 | ||||
-rw-r--r-- | remoting/client/decoder_verbatim.cc | 56 | ||||
-rw-r--r-- | remoting/client/decoder_verbatim.h | 16 | ||||
-rw-r--r-- | remoting/client/host_connection.cc | 16 | ||||
-rw-r--r-- | remoting/client/simple_client.cc | 84 | ||||
-rw-r--r-- | remoting/client/x11_client.cc | 262 | ||||
-rw-r--r-- | remoting/client/x11_view.cc | 151 | ||||
-rw-r--r-- | remoting/client/x11_view.h | 56 |
11 files changed, 683 insertions, 133 deletions
diff --git a/remoting/client/chromoting_view.h b/remoting/client/chromoting_view.h new file mode 100644 index 0000000..cf70b99 --- /dev/null +++ b/remoting/client/chromoting_view.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_CHROMOTING_VIEW_H_ +#define REMOTING_CLIENT_CHROMOTING_VIEW_H_ + +#include "base/ref_counted.h" + +namespace remoting { + +class HostMessage; + +// ChromotingView defines the behavior of an object that draws a view of the +// remote desktop. Its main function is to choose the right decoder and render +// the update stream onto the screen. +class ChromotingView : public base::RefCountedThreadSafe<ChromotingView> { + public: + virtual ~ChromotingView() {} + + // Tells the ChromotingView to paint the current image on the screen. + // TODO(hclam): Add rects as parameter if needed. + virtual void Paint() = 0; + + // Handle the BeginUpdateStream message. + virtual void HandleBeginUpdateStream(HostMessage* msg) = 0; + + // Handle the UpdateStreamPacket message. + virtual void HandleUpdateStreamPacket(HostMessage* msg) = 0; + + // Handle the EndUpdateStream message. + virtual void HandleEndUpdateStream(HostMessage* msg) = 0; +}; + +} // namespace remoting + +#endif // REMOTING_CLIENT_CHROMOTING_VIEW_H_ diff --git a/remoting/client/client_util.cc b/remoting/client/client_util.cc new file mode 100644 index 0000000..66906f8 --- /dev/null +++ b/remoting/client/client_util.cc @@ -0,0 +1,76 @@ +// 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/client_util.h" + +#include <iostream> + +static 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 +} + +namespace remoting { + +// Get host JID from command line arguments, or stdin if not specified. +bool GetLoginInfo(std::string& host_jid, + std::string& username, + std::string& password) { + 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 false; + } + + // Get username (JID). + // Extract default JID from host_jid. + 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). + SetConsoleEcho(false); + std::cout << "Password: "; + getline(std::cin, password); + SetConsoleEcho(true); + std::cout << std::endl; + return true; +} + +} // namespace remoting diff --git a/remoting/client/client_util.h b/remoting/client/client_util.h new file mode 100644 index 0000000..4c2902e --- /dev/null +++ b/remoting/client/client_util.h @@ -0,0 +1,19 @@ +// 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_CLIENT_UTIL_H_ +#define REMOTING_CLIENT_CLIENT_UTIL_H_ + +#include <string> + +namespace remoting { + +// Get the login info from the console and writes into |host_jid|, |username| +// and |password|. Return true if successful. +bool GetLoginInfo(std::string& host_jid, std::string& username, + std::string& password); + +} // namespace remoting + +#endif // REMOTING_CLIENT_CLIENT_UTIL_H_ diff --git a/remoting/client/decoder.h b/remoting/client/decoder.h index d39be07..ac2fe4d 100644 --- a/remoting/client/decoder.h +++ b/remoting/client/decoder.h @@ -7,7 +7,7 @@ #include <vector> -#include "base/callback.h" +#include "base/task.h" #include "base/scoped_ptr.h" #include "gfx/rect.h" #include "media/base/video_frame.h" @@ -15,12 +15,15 @@ namespace remoting { +// TODO(hclam): Merge this with the one in remoting/host/encoder.h. +typedef std::vector<gfx::Rect> UpdatedRects; + // 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) +// 1. BeginDecode(PartialDecodeDone, DecodeDone, VideoFrame) // 2. PartialDecode(HostMessage) // ... // 3. EndDecode() @@ -34,27 +37,22 @@ namespace remoting { // 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. + // If decode is partially done and |frame| can be read, |partial_decode_done| + // is called and |update_rects| contains the updated regions. + // If decode is completed |decode_done| is called. // 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; + virtual bool BeginDecode(scoped_refptr<media::VideoFrame> frame, + UpdatedRects* updated_rects, + Task* partial_decode_done, + Task* decode_done) = 0; // Give a HostMessage that contains the update stream packet that contains // the encoded data to the decoder. @@ -62,7 +60,7 @@ class Decoder { // 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; + virtual bool PartialDecode(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 @@ -70,21 +68,6 @@ class Decoder { // 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 diff --git a/remoting/client/decoder_verbatim.cc b/remoting/client/decoder_verbatim.cc index 7cbf428..8fb54ac 100644 --- a/remoting/client/decoder_verbatim.cc +++ b/remoting/client/decoder_verbatim.cc @@ -6,62 +6,80 @@ namespace remoting { -bool DecoderVerbatim::BeginDecode(scoped_refptr<media::VideoFrame> frame) { - // TODO(hclam): Check if we can accept the codec. +bool DecoderVerbatim::BeginDecode(scoped_refptr<media::VideoFrame> frame, + UpdatedRects* updated_rects, + Task* partial_decode_done, + Task* decode_done) { + DCHECK(!partial_decode_done_.get()); + DCHECK(!decode_done_.get()); + DCHECK(!updated_rects_); + + partial_decode_done_.reset(partial_decode_done); + decode_done_.reset(decode_done); + updated_rects_ = updated_rects; + + // TODO(hclam): Check if we can accept the color format of the video frame and + // the codec. frame_ = frame; return true; } -bool DecoderVerbatim::PartialDecode(chromotocol_pb::HostMessage* message) { - scoped_ptr<chromotocol_pb::HostMessage> msg_deleter(message); +bool DecoderVerbatim::PartialDecode(HostMessage* message) { + scoped_ptr<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 = + 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) { + if (pixel_format == PixelFormatRgb24) { bytes_per_pixel = 3; - } else if (pixel_format == chromotocol_pb::PixelFormatRgb565) { + } else if (pixel_format == PixelFormatRgb565) { bytes_per_pixel = 2; - } else if (pixel_format == chromotocol_pb::PixelFormatRgb32) { + } else if (pixel_format == PixelFormatRgb32) { bytes_per_pixel = 4; - } else if (pixel_format != chromotocol_pb::PixelFormatAscii) { + } else if (pixel_format != PixelFormatAscii) { bytes_per_pixel = 1; } else { NOTREACHED() << "Pixel format not supported"; } + if (static_cast<PixelFormat>(frame_->format()) != pixel_format) { + NOTREACHED() << "Pixel format of message doesn't match the video frame. " + "Expected vs received = " + << frame_->format() << " vs " << pixel_format + << " Color space conversion required."; + } + // 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; + 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); + updated_rects_->clear(); + updated_rects_->push_back(gfx::Rect(x, y, width, height)); + partial_decode_done_->Run(); return true; } void DecoderVerbatim::EndDecode() { - decode_done()->Run(frame_); + decode_done_->Run(); + + partial_decode_done_.reset(); + decode_done_.reset(); frame_ = NULL; + updated_rects_ = NULL; } } // namespace remoting diff --git a/remoting/client/decoder_verbatim.h b/remoting/client/decoder_verbatim.h index 94d2f81..5d18a48 100644 --- a/remoting/client/decoder_verbatim.h +++ b/remoting/client/decoder_verbatim.h @@ -11,19 +11,25 @@ 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) { + DecoderVerbatim() { } // Decoder implementations. - virtual bool BeginDecode(scoped_refptr<media::VideoFrame> frame); - virtual bool PartialDecode(chromotocol_pb::HostMessage* message); + virtual bool BeginDecode(scoped_refptr<media::VideoFrame> frame, + UpdatedRects* update_rects, + Task* partial_decode_done, + Task* decode_done); + virtual bool PartialDecode(HostMessage* message); virtual void EndDecode(); private: + // Tasks to call when decode is done. + scoped_ptr<Task> partial_decode_done_; + scoped_ptr<Task> decode_done_; + // The video frame to write to. scoped_refptr<media::VideoFrame> frame_; + UpdatedRects* updated_rects_; DISALLOW_COPY_AND_ASSIGN(DecoderVerbatim); }; diff --git a/remoting/client/host_connection.cc b/remoting/client/host_connection.cc index 00c542e..cc73b73 100644 --- a/remoting/client/host_connection.cc +++ b/remoting/client/host_connection.cc @@ -26,22 +26,28 @@ void HostConnection::Connect(const std::string& username, } void HostConnection::Disconnect() { - if (jingle_channel_.get()) + // TODO(hclam): It's not thread safe to read the state. + if (jingle_channel_.get() && + jingle_channel_->state() != JingleChannel::CLOSED) { jingle_channel_->Close(); + } - if (jingle_client_.get()) + // TODO(hclam): It's not thread safe to read the state. + if (jingle_client_.get() && + jingle_client_->state() != JingleClient::CLOSED) { jingle_client_->Close(); + } } void HostConnection::OnStateChange(JingleChannel* channel, JingleChannel::State state) { DCHECK(handler_); - if (state == JingleChannel::FAILED) + if (state == JingleChannel::OPEN) + handler_->OnConnectionOpened(this); + else 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, diff --git a/remoting/client/simple_client.cc b/remoting/client/simple_client.cc index da65b2a..474f02f 100644 --- a/remoting/client/simple_client.cc +++ b/remoting/client/simple_client.cc @@ -11,47 +11,20 @@ #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/client_util.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::BeginUpdateStreamMessage; +using remoting::EndUpdateStreamMessage; using remoting::HostConnection; +using remoting::HostMessage; using remoting::HostMessageList; +using remoting::InitClientMessage; 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 -} +using remoting::UpdateStreamPacketMessage; class SimpleHostEventHandler : public HostConnection::EventHandler { public: @@ -137,50 +110,13 @@ class SimpleHostEventHandler : public HostConnection::EventHandler { int main(int argc, char** argv) { base::AtExitManager exit_manager; + std::string host_jid, username, auth_token; - 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; + if (remoting::GetLoginInfo(host_jid, username, auth_token)) { + std::cerr << "Cannot get valid login info." << 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 auth token. - std::string auth_token; - std::cout << "Auth Token: "; - getline(std::cin, auth_token); - // The message loop that everything runs on. MessageLoop main_loop; SimpleHostEventHandler handler(&main_loop); diff --git a/remoting/client/x11_client.cc b/remoting/client/x11_client.cc new file mode 100644 index 0000000..6b8e4e2 --- /dev/null +++ b/remoting/client/x11_client.cc @@ -0,0 +1,262 @@ +// 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 file implements a X11 chromoting client. + +#include <iostream> + +#include "base/at_exit.h" +#include "base/message_loop.h" +#include "base/stl_util-inl.h" +#include "remoting/client/host_connection.h" +#include "remoting/client/client_util.h" + +// Include Xlib at the end because it clashes with ClientMessage defined in +// the protocol buffer. +// x11_view.h also includes Xlib.h so put it behind all other headers but +// before Xlib.h +#include "remoting/client/x11_view.h" +#include <X11/Xlib.h> + +namespace remoting { + +class X11Client : public base::RefCountedThreadSafe<X11Client>, + public HostConnection::EventHandler { + public: + X11Client(std::string host_jid, std::string username, std::string auth_token) + : display_(NULL), + window_(0), + width_(0), + height_(0), + host_jid_(host_jid), + username_(username), + auth_token_(auth_token) { + } + + virtual ~X11Client() { + DCHECK(!display_); + DCHECK(!window_); + } + + // Starts the remoting client and the message loop. Returns only after + // the message loop has terminated. + void Run() { + message_loop_.PostTask(FROM_HERE, + NewRunnableMethod(this, &X11Client::DoInitX11)); + message_loop_.PostTask(FROM_HERE, + NewRunnableMethod(this, &X11Client::DoInitConnection)); + message_loop_.Run(); + } + + //////////////////////////////////////////////////////////////////////////// + // HostConnection::EventHandler implementations. + virtual void HandleMessages(HostConnection* conn, + remoting::HostMessageList* messages) { + for (size_t i = 0; i < messages->size(); ++i) { + HostMessage* msg = (*messages)[i]; + if (msg->has_init_client()) { + message_loop_.PostTask( + FROM_HERE, NewRunnableMethod(this, &X11Client::DoInitClient, msg)); + } else if (msg->has_begin_update_stream()) { + message_loop_.PostTask( + FROM_HERE, + NewRunnableMethod(this, &X11Client::DoBeginUpdate, msg)); + } else if (msg->has_update_stream_packet()) { + message_loop_.PostTask( + FROM_HERE, + NewRunnableMethod(this, &X11Client::DoHandleUpdate, msg)); + } else if (msg->has_end_update_stream()) { + message_loop_.PostTask( + FROM_HERE, NewRunnableMethod(this, &X11Client::DoEndUpdate, msg)); + } else { + NOTREACHED() << "Unknown message received"; + } + } + // Assume we have processed all the messages. + messages->clear(); + } + + virtual void OnConnectionOpened(HostConnection* conn) { + std::cout << "Connection establised." << std::endl; + } + + virtual void OnConnectionClosed(HostConnection* conn) { + std::cout << "Connection closed." << std::endl; + Exit(); + } + + virtual void OnConnectionFailed(HostConnection* conn) { + std::cout << "Conection failed." << std::endl; + Exit(); + } + + private: + void DoInitX11() { + display_ = XOpenDisplay(NULL); + if (!display_) { + std::cout << "Error - cannot open display" << std::endl; + Exit(); + } + + // Get properties of the screen. + int screen = DefaultScreen(display_); + int root_window = RootWindow(display_, screen); + + // Creates the window. + window_ = XCreateSimpleWindow(display_, root_window, 1, 1, 640, 480, 0, + BlackPixel(display_, screen), + BlackPixel(display_, screen)); + DCHECK(window_); + XStoreName(display_, window_, "X11 Remoting"); + + // Specifies what kind of messages we want to receive. + XSelectInput(display_, window_, ExposureMask | ButtonPressMask); + XMapWindow(display_, window_); + } + + void DoInitConnection() { + // If the initialization of X11 has failed then return directly. + if (!display_) + return; + + // Creates a HostConnection object and connection to the host. + LOG(INFO) << "Connecting..."; + connection_.reset(new HostConnection(new ProtocolDecoder(), this)); + connection_->Connect(username_, auth_token_, host_jid_); + } + + void DoDestroyX11() { + if (display_ && window_) { + // Shutdown the window system. + XDestroyWindow(display_, window_); + XCloseDisplay(display_); + display_ = NULL; + window_ = 0; + } + } + + void DoDisconnect() { + if (connection_.get()) + connection_->Disconnect(); + } + + void Exit() { + // Disconnect the jingle channel and client. + message_loop_.PostTask(FROM_HERE, + NewRunnableMethod(this, &X11Client::DoDisconnect)); + + // Post a task to shutdown X11. + message_loop_.PostTask(FROM_HERE, + NewRunnableMethod(this, &X11Client::DoDestroyX11)); + + // Quit the current message loop. + message_loop_.PostTask(FROM_HERE, new MessageLoop::QuitTask()); + } + + // This method is executed on the main loop. + void DoInitClient(HostMessage* msg) { + DCHECK_EQ(&message_loop_, MessageLoop::current()); + DCHECK(msg->has_init_client()); + scoped_ptr<HostMessage> deleter(msg); + + // Saves the dimension and resize the window. + width_ = msg->init_client().width(); + height_ = msg->init_client().height(); + LOG(INFO) << "Init client receievd: " << width_ << "x" << height_; + XResizeWindow(display_, window_, width_, height_); + + // Now construct the X11View that renders the remote desktop. + view_ = new X11View(display_, window_, width_, height_); + + // Schedule the event handler to process the event queue of X11. + ScheduleX11EventHandler(); + } + + // The following methods are executed on the same thread as libjingle. + void DoBeginUpdate(HostMessage* msg) { + DCHECK_EQ(&message_loop_, MessageLoop::current()); + DCHECK(msg->has_begin_update_stream()); + + view_->HandleBeginUpdateStream(msg); + } + + void DoHandleUpdate(HostMessage* msg) { + DCHECK_EQ(&message_loop_, MessageLoop::current()); + DCHECK(msg->has_update_stream_packet()); + + view_->HandleUpdateStreamPacket(msg); + } + + void DoEndUpdate(HostMessage* msg) { + DCHECK_EQ(&message_loop_, MessageLoop::current()); + DCHECK(msg->has_end_update_stream()); + + view_->HandleEndUpdateStream(msg); + } + + void DoProcessX11Events() { + DCHECK_EQ(&message_loop_, MessageLoop::current()); + if (XPending(display_)) { + XEvent e; + XNextEvent(display_, &e); + if (e.type == Expose) { + // Tell the ChromotingView to paint again. + view_->Paint(); + } else if (e.type == ButtonPress) { + // TODO(hclam): Implement. + NOTIMPLEMENTED(); + } else { + // TODO(hclam): Implement. + NOTIMPLEMENTED(); + } + } + + // Schedule the next event handler. + ScheduleX11EventHandler(); + } + + void ScheduleX11EventHandler() { + // Schedule a delayed task to process X11 events in 10ms. + static const int kProcessEventsInterval = 10; + message_loop_.PostDelayedTask( + FROM_HERE, + NewRunnableMethod(this, &X11Client::DoProcessX11Events), + kProcessEventsInterval); + } + + // Members used for display. + Display* display_; + Window window_; + + // Dimension of the window. They are initialized when InitClient message is + // received. + int width_; + int height_; + + std::string host_jid_; + std::string username_; + std::string auth_token_; + scoped_ptr<HostConnection> connection_; + scoped_refptr<ChromotingView> view_; + + MessageLoop message_loop_; + + DISALLOW_COPY_AND_ASSIGN(X11Client); +}; + +} // namespace remoting + +int main() { + base::AtExitManager at_exit; + std::string host_jid, username, auth_token; + + if (!remoting::GetLoginInfo(host_jid, username, auth_token)) { + std::cout << "Cannot obtain login info" << std::endl; + return 1; + } + + scoped_refptr<remoting::X11Client> client = + new remoting::X11Client(host_jid, username, auth_token); + client->Run(); +} diff --git a/remoting/client/x11_view.cc b/remoting/client/x11_view.cc new file mode 100644 index 0000000..bdb54d4 --- /dev/null +++ b/remoting/client/x11_view.cc @@ -0,0 +1,151 @@ +// 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/x11_view.h" + +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <X11/extensions/Xrender.h> +#include <X11/extensions/Xcomposite.h> +#include "remoting/client/decoder_verbatim.h" + +namespace remoting { + +X11View::X11View(Display* display, int window, int width, int height) + : display_(display), + window_(window), + picture_(0), + width_(width), + height_(height) { +} + +X11View::~X11View() { +} + +void X11View::Paint() { + // TODO(hclam): Paint only the updated regions. + all_update_rects_.clear(); + + // If we have not initialized the render target then do it now. + if (!frame_) + InitPaintTarget(); + + // Upload the image to a pixmap. And then creats a picture from the pixmap + // and composite the picture over the picture represending the window. + + // Creates a XImage. + XImage image; + memset(&image, 0, sizeof(image)); + image.width = width_; + image.height = height_; + image.depth = 32; + image.bits_per_pixel = 32; + image.format = ZPixmap; + image.byte_order = LSBFirst; + image.bitmap_unit = 8; + image.bitmap_bit_order = LSBFirst; + image.bytes_per_line = frame_->stride(media::VideoFrame::kRGBPlane); + image.red_mask = 0xff; + image.green_mask = 0xff00; + image.blue_mask = 0xff0000; + image.data = reinterpret_cast<char*>( + frame_->data(media::VideoFrame::kRGBPlane)); + + // Creates a pixmap and uploads from the XImage. + unsigned long pixmap = XCreatePixmap(display_, window_, width_, height_, 32); + + GC gc = XCreateGC(display_, pixmap, 0, NULL); + XPutImage(display_, pixmap, gc, &image, 0, 0, 0, 0, width_, height_); + XFreeGC(display_, gc); + + // Creates the picture representing the pixmap. + unsigned long picture = XRenderCreatePicture( + display_, pixmap, + XRenderFindStandardFormat(display_, PictStandardARGB32), + 0, NULL); + + // Composite the picture over the picture representing the window. + XRenderComposite(display_, PictOpSrc, picture, 0, + picture_, 0, 0, 0, 0, 0, 0, + width_, height_); + + XRenderFreePicture(display_, picture); + XFreePixmap(display_, pixmap); +} + +void X11View::InitPaintTarget() { + // Testing XRender support. + int dummy; + bool xrender_support = XRenderQueryExtension(display_, &dummy, &dummy); + CHECK(xrender_support) << "XRender is not supported!"; + + XWindowAttributes attr; + XGetWindowAttributes(display_, window_, &attr); + + XRenderPictFormat* pictformat = XRenderFindVisualFormat( + display_, + attr.visual); + CHECK(pictformat) << "XRENDER does not support default visual"; + + picture_ = XRenderCreatePicture(display_, window_, pictformat, 0, NULL); + CHECK(picture_) << "Backing picture not created"; + + // Create the video frame to carry the decoded image. + media::VideoFrame::CreateFrame(media::VideoFrame::RGB32, width_, height_, + base::TimeDelta(), base::TimeDelta(), &frame_); + DCHECK(frame_); +} + +void X11View::HandleBeginUpdateStream(HostMessage* msg) { + scoped_ptr<HostMessage> deleter(msg); + + // TODO(hclam): Use the information from the message to create the decoder. + // We lazily construct the decoder. + if (!decoder_.get()) { + decoder_.reset(new DecoderVerbatim()); + } + + // Tell the decoder to do start decoding. + decoder_->BeginDecode(frame_, &update_rects_, + NewRunnableMethod(this, &X11View::OnPartialDecodeDone), + NewRunnableMethod(this, &X11View::OnDecodeDone)); +} + +void X11View::HandleUpdateStreamPacket(HostMessage* msg) { + decoder_->PartialDecode(msg); +} + +void X11View::HandleEndUpdateStream(HostMessage* msg) { + scoped_ptr<HostMessage> deleter(msg); + decoder_->EndDecode(); +} + +void X11View::OnPartialDecodeDone() { + // Decoder has produced some output so schedule a paint. We'll get a Paint() + // call in the short future. Note that we can get UpdateStreamPacket during + // this short period of time and we will perform decode again and the + // information of updated rects will be lost. + // There are several ways to solve this problem. + // 1. Merge the updated rects and perform one paint. + // 2. Queue the updated rects and perform two paints. + // 3. Ignore the updated rects and always paint the full image. Since we + // use one frame as output this will always be correct. + // We will take (1) and simply concat the list of rectangles. + all_update_rects_.insert(all_update_rects_.begin() + + all_update_rects_.size(), + update_rects_.begin(), update_rects_.end()); + + // TODO(hclam): Make sure we call this method on the right thread. Since + // decoder is single-threaded we don't have a problem but we better post + // a task to do the right thing. + XEvent event; + event.type = Expose; + XSendEvent(display_, static_cast<int>(window_), true, ExposureMask, &event); +} + +void X11View::OnDecodeDone() { + // Since we do synchronous decoding here there's nothing in this method. +} + +} // namespace remoting diff --git a/remoting/client/x11_view.h b/remoting/client/x11_view.h new file mode 100644 index 0000000..1959e18 --- /dev/null +++ b/remoting/client/x11_view.h @@ -0,0 +1,56 @@ +// 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_X11_VIEW_H_ +#define REMOTING_CLIENT_X11_VIEW_H_ + +#include "base/basictypes.h" +#include "base/scoped_ptr.h" +#include "media/base/video_frame.h" +#include "remoting/client/decoder.h" +#include "remoting/client/chromoting_view.h" + +typedef struct _XDisplay Display; + +namespace remoting { + +// A ChromotingView implemented using X11 and XRender. +class X11View : public ChromotingView { + public: + X11View(Display* display, int window, int width, int height); + + virtual ~X11View(); + + // ChromotingView implementations. + virtual void Paint(); + virtual void HandleBeginUpdateStream(HostMessage* msg); + virtual void HandleUpdateStreamPacket(HostMessage* msg); + virtual void HandleEndUpdateStream(HostMessage* msg) ; + + private: + void InitPaintTarget(); + void OnPartialDecodeDone(); + void OnDecodeDone(); + + Display* display_; + int window_; + int width_; + int height_; + + // A picture created in the X server that represents drawing area of the + // window. + int picture_; + + scoped_refptr<media::VideoFrame> frame_; + UpdatedRects update_rects_; + UpdatedRects all_update_rects_; + + scoped_ptr<Decoder> decoder_; + + DISALLOW_COPY_AND_ASSIGN(X11View); +}; + +} // namespace remoting + +#endif // REMOTING_CLIENT_X11_VIEW_H_ |