root/remoting/host/desktop_session_agent.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. InjectClipboardEvent
  2. Create
  3. SharedBuffer
  4. shared_memory_
  5. started_
  6. OnMessageReceived
  7. OnChannelConnected
  8. OnChannelError
  9. CreateSharedMemory
  10. client_jid
  11. DisconnectSession
  12. OnLocalMouseMoved
  13. SetDisableInputs
  14. OnStartSessionAgent
  15. OnCaptureCompleted
  16. OnCursorShapeChanged
  17. InjectClipboardEvent
  18. ProcessAudioPacket
  19. Start
  20. Stop
  21. OnCaptureFrame
  22. OnInjectClipboardEvent
  23. OnInjectKeyEvent
  24. OnInjectTextEvent
  25. OnInjectMouseEvent
  26. SetScreenResolution
  27. SendToNetwork
  28. StartAudioCapturer
  29. StopAudioCapturer
  30. StartVideoCapturer
  31. StopVideoCapturer
  32. OnSharedBufferDeleted
  33. CloseDesktopPipeHandle

// Copyright (c) 2012 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/desktop_session_agent.h"

#include "base/file_util.h"
#include "base/logging.h"
#include "base/memory/shared_memory.h"
#include "ipc/ipc_channel_proxy.h"
#include "ipc/ipc_message.h"
#include "ipc/ipc_message_macros.h"
#include "remoting/base/auto_thread_task_runner.h"
#include "remoting/base/constants.h"
#include "remoting/host/audio_capturer.h"
#include "remoting/host/chromoting_messages.h"
#include "remoting/host/desktop_environment.h"
#include "remoting/host/input_injector.h"
#include "remoting/host/ipc_util.h"
#include "remoting/host/remote_input_filter.h"
#include "remoting/host/screen_controls.h"
#include "remoting/host/screen_resolution.h"
#include "remoting/proto/audio.pb.h"
#include "remoting/proto/control.pb.h"
#include "remoting/proto/event.pb.h"
#include "remoting/protocol/clipboard_stub.h"
#include "remoting/protocol/input_event_tracker.h"
#include "third_party/webrtc/modules/desktop_capture/desktop_geometry.h"
#include "third_party/webrtc/modules/desktop_capture/desktop_frame.h"
#include "third_party/webrtc/modules/desktop_capture/shared_memory.h"

namespace remoting {

namespace {

// Routes local clipboard events though the IPC channel to the network process.
class DesktopSesssionClipboardStub : public protocol::ClipboardStub {
 public:
  explicit DesktopSesssionClipboardStub(
      scoped_refptr<DesktopSessionAgent> desktop_session_agent);
  virtual ~DesktopSesssionClipboardStub();

  // protocol::ClipboardStub implementation.
  virtual void InjectClipboardEvent(
      const protocol::ClipboardEvent& event) OVERRIDE;

 private:
  scoped_refptr<DesktopSessionAgent> desktop_session_agent_;

  DISALLOW_COPY_AND_ASSIGN(DesktopSesssionClipboardStub);
};

DesktopSesssionClipboardStub::DesktopSesssionClipboardStub(
    scoped_refptr<DesktopSessionAgent> desktop_session_agent)
    : desktop_session_agent_(desktop_session_agent) {
}

DesktopSesssionClipboardStub::~DesktopSesssionClipboardStub() {
}

void DesktopSesssionClipboardStub::InjectClipboardEvent(
    const protocol::ClipboardEvent& event) {
  desktop_session_agent_->InjectClipboardEvent(event);
}

}  // namespace

// webrtc::SharedMemory implementation that notifies creating
// DesktopSessionAgent when it's deleted.
class DesktopSessionAgent::SharedBuffer : public webrtc::SharedMemory {
 public:
  static scoped_ptr<SharedBuffer> Create(DesktopSessionAgent* agent,
                                         size_t size,
                                         int id) {
    scoped_ptr<base::SharedMemory> memory(new base::SharedMemory());
    if (!memory->CreateAndMapAnonymous(size))
      return scoped_ptr<SharedBuffer>();
    return scoped_ptr<SharedBuffer>(
        new SharedBuffer(agent, memory.Pass(), size, id));
  }

  virtual ~SharedBuffer() {
    agent_->OnSharedBufferDeleted(id());
  }

 private:
  SharedBuffer(DesktopSessionAgent* agent,
               scoped_ptr<base::SharedMemory> memory,
               size_t size,
               int id)
      : SharedMemory(memory->memory(), size,
#if defined(OS_WIN)
                     memory->handle(),
#else
                     memory->handle().fd,
#endif
                     id),
        agent_(agent),
        shared_memory_(memory.Pass()) {
  }

  DesktopSessionAgent* agent_;
  scoped_ptr<base::SharedMemory> shared_memory_;

  DISALLOW_COPY_AND_ASSIGN(SharedBuffer);
};

DesktopSessionAgent::Delegate::~Delegate() {
}

DesktopSessionAgent::DesktopSessionAgent(
    scoped_refptr<AutoThreadTaskRunner> audio_capture_task_runner,
    scoped_refptr<AutoThreadTaskRunner> caller_task_runner,
    scoped_refptr<AutoThreadTaskRunner> input_task_runner,
    scoped_refptr<AutoThreadTaskRunner> io_task_runner,
    scoped_refptr<AutoThreadTaskRunner> video_capture_task_runner)
    : audio_capture_task_runner_(audio_capture_task_runner),
      caller_task_runner_(caller_task_runner),
      input_task_runner_(input_task_runner),
      io_task_runner_(io_task_runner),
      video_capture_task_runner_(video_capture_task_runner),
      control_factory_(this),
      desktop_pipe_(IPC::InvalidPlatformFileForTransit()),
      next_shared_buffer_id_(1),
      shared_buffers_(0),
      started_(false) {
  DCHECK(caller_task_runner_->BelongsToCurrentThread());
}

bool DesktopSessionAgent::OnMessageReceived(const IPC::Message& message) {
  DCHECK(caller_task_runner_->BelongsToCurrentThread());

  bool handled = true;
  if (started_) {
    IPC_BEGIN_MESSAGE_MAP(DesktopSessionAgent, message)
      IPC_MESSAGE_HANDLER(ChromotingNetworkDesktopMsg_CaptureFrame,
                          OnCaptureFrame)
      IPC_MESSAGE_HANDLER(ChromotingNetworkDesktopMsg_InjectClipboardEvent,
                          OnInjectClipboardEvent)
      IPC_MESSAGE_HANDLER(ChromotingNetworkDesktopMsg_InjectKeyEvent,
                          OnInjectKeyEvent)
      IPC_MESSAGE_HANDLER(ChromotingNetworkDesktopMsg_InjectTextEvent,
                          OnInjectTextEvent)
      IPC_MESSAGE_HANDLER(ChromotingNetworkDesktopMsg_InjectMouseEvent,
                          OnInjectMouseEvent)
      IPC_MESSAGE_HANDLER(ChromotingNetworkDesktopMsg_SetScreenResolution,
                          SetScreenResolution)
      IPC_MESSAGE_UNHANDLED(handled = false)
    IPC_END_MESSAGE_MAP()
  } else {
    IPC_BEGIN_MESSAGE_MAP(DesktopSessionAgent, message)
      IPC_MESSAGE_HANDLER(ChromotingNetworkDesktopMsg_StartSessionAgent,
                          OnStartSessionAgent)
      IPC_MESSAGE_UNHANDLED(handled = false)
    IPC_END_MESSAGE_MAP()
  }

  CHECK(handled) << "Received unexpected IPC type: " << message.type();
  return handled;
}

void DesktopSessionAgent::OnChannelConnected(int32 peer_pid) {
  DCHECK(caller_task_runner_->BelongsToCurrentThread());

  VLOG(1) << "IPC: desktop <- network (" << peer_pid << ")";

  CloseDesktopPipeHandle();
}

void DesktopSessionAgent::OnChannelError() {
  DCHECK(caller_task_runner_->BelongsToCurrentThread());

  // Make sure the channel is closed.
  network_channel_.reset();
  CloseDesktopPipeHandle();

  // Notify the caller that the channel has been disconnected.
  if (delegate_.get())
    delegate_->OnNetworkProcessDisconnected();
}

webrtc::SharedMemory* DesktopSessionAgent::CreateSharedMemory(size_t size) {
  DCHECK(video_capture_task_runner_->BelongsToCurrentThread());

  scoped_ptr<SharedBuffer> buffer =
      SharedBuffer::Create(this, size, next_shared_buffer_id_);
  if (buffer) {
    shared_buffers_++;

    // |next_shared_buffer_id_| starts from 1 and incrementing it by 2 makes
    // sure it is always odd and therefore zero is never used as a valid buffer
    // ID.
    //
    // It is very unlikely (though theoretically possible) to allocate the same
    // ID for two different buffers due to integer overflow. It should take
    // about a year of allocating 100 new buffers every second. Practically
    // speaking it never happens.
    next_shared_buffer_id_ += 2;

    IPC::PlatformFileForTransit handle;
#if defined(OS_WIN)
    handle = buffer->handle();
#else
    handle = base::FileDescriptor(buffer->handle(), false);
#endif
    SendToNetwork(new ChromotingDesktopNetworkMsg_CreateSharedBuffer(
        buffer->id(), handle, buffer->size()));
  }

  return buffer.release();
}

DesktopSessionAgent::~DesktopSessionAgent() {
  DCHECK(!audio_capturer_);
  DCHECK(!desktop_environment_);
  DCHECK(!network_channel_);
  DCHECK(!screen_controls_);
  DCHECK(!video_capturer_);

  CloseDesktopPipeHandle();
}

const std::string& DesktopSessionAgent::client_jid() const {
  return client_jid_;
}

void DesktopSessionAgent::DisconnectSession() {
  SendToNetwork(new ChromotingDesktopNetworkMsg_DisconnectSession());
}

void DesktopSessionAgent::OnLocalMouseMoved(
    const webrtc::DesktopVector& new_pos) {
  DCHECK(caller_task_runner_->BelongsToCurrentThread());

  remote_input_filter_->LocalMouseMoved(new_pos);
}

void DesktopSessionAgent::SetDisableInputs(bool disable_inputs) {
  DCHECK(caller_task_runner_->BelongsToCurrentThread());

  // Do not expect this method to be called because it is only used by It2Me.
  NOTREACHED();
}

void DesktopSessionAgent::OnStartSessionAgent(
    const std::string& authenticated_jid,
    const ScreenResolution& resolution,
    bool virtual_terminal) {
  DCHECK(caller_task_runner_->BelongsToCurrentThread());
  DCHECK(!started_);
  DCHECK(!audio_capturer_);
  DCHECK(!desktop_environment_);
  DCHECK(!input_injector_);
  DCHECK(!screen_controls_);
  DCHECK(!video_capturer_);

  started_ = true;
  client_jid_ = authenticated_jid;

  // Enable the curtain mode.
  delegate_->desktop_environment_factory().SetEnableCurtaining(
      virtual_terminal);

  // Create a desktop environment for the new session.
  desktop_environment_ = delegate_->desktop_environment_factory().Create(
      control_factory_.GetWeakPtr());

  // Create the session controller and set the initial screen resolution.
  screen_controls_ = desktop_environment_->CreateScreenControls();
  SetScreenResolution(resolution);

  // Create the input injector.
  input_injector_ = desktop_environment_->CreateInputInjector();

  // Hook up the input filter.
  input_tracker_.reset(new protocol::InputEventTracker(input_injector_.get()));
  remote_input_filter_.reset(new RemoteInputFilter(input_tracker_.get()));

#if defined(OS_WIN)
  // LocalInputMonitorWin filters out an echo of the injected input before it
  // reaches |remote_input_filter_|.
  remote_input_filter_->SetExpectLocalEcho(false);
#endif  // defined(OS_WIN)

  // Start the input injector.
  scoped_ptr<protocol::ClipboardStub> clipboard_stub(
      new DesktopSesssionClipboardStub(this));
  input_injector_->Start(clipboard_stub.Pass());

  // Start the audio capturer.
  if (delegate_->desktop_environment_factory().SupportsAudioCapture()) {
    audio_capturer_ = desktop_environment_->CreateAudioCapturer();
    audio_capture_task_runner_->PostTask(
        FROM_HERE, base::Bind(&DesktopSessionAgent::StartAudioCapturer, this));
  }

  // Start the video capturer.
  video_capturer_ = desktop_environment_->CreateVideoCapturer();
  video_capture_task_runner_->PostTask(
      FROM_HERE, base::Bind(&DesktopSessionAgent::StartVideoCapturer, this));
}

void DesktopSessionAgent::OnCaptureCompleted(webrtc::DesktopFrame* frame) {
  DCHECK(video_capture_task_runner_->BelongsToCurrentThread());

  last_frame_.reset(frame);

  current_size_ = frame->size();

  // Serialize webrtc::DesktopFrame.
  SerializedDesktopFrame serialized_frame;
  serialized_frame.shared_buffer_id = frame->shared_memory()->id();
  serialized_frame.bytes_per_row = frame->stride();
  serialized_frame.dimensions = frame->size();
  serialized_frame.capture_time_ms = frame->capture_time_ms();
  serialized_frame.dpi = frame->dpi();
  for (webrtc::DesktopRegion::Iterator i(frame->updated_region());
       !i.IsAtEnd(); i.Advance()) {
    serialized_frame.dirty_region.push_back(i.rect());
  }

  SendToNetwork(
      new ChromotingDesktopNetworkMsg_CaptureCompleted(serialized_frame));
}

void DesktopSessionAgent::OnCursorShapeChanged(
    webrtc::MouseCursorShape* cursor_shape) {
  DCHECK(video_capture_task_runner_->BelongsToCurrentThread());

  scoped_ptr<webrtc::MouseCursorShape> owned_cursor(cursor_shape);

  SendToNetwork(new ChromotingDesktopNetworkMsg_CursorShapeChanged(
      *cursor_shape));
}

void DesktopSessionAgent::InjectClipboardEvent(
    const protocol::ClipboardEvent& event) {
  DCHECK(caller_task_runner_->BelongsToCurrentThread());

  std::string serialized_event;
  if (!event.SerializeToString(&serialized_event)) {
    LOG(ERROR) << "Failed to serialize protocol::ClipboardEvent.";
    return;
  }

  SendToNetwork(
      new ChromotingDesktopNetworkMsg_InjectClipboardEvent(serialized_event));
}

void DesktopSessionAgent::ProcessAudioPacket(scoped_ptr<AudioPacket> packet) {
  DCHECK(audio_capture_task_runner_->BelongsToCurrentThread());

  std::string serialized_packet;
  if (!packet->SerializeToString(&serialized_packet)) {
    LOG(ERROR) << "Failed to serialize AudioPacket.";
    return;
  }

  SendToNetwork(new ChromotingDesktopNetworkMsg_AudioPacket(serialized_packet));
}

bool DesktopSessionAgent::Start(const base::WeakPtr<Delegate>& delegate,
                                IPC::PlatformFileForTransit* desktop_pipe_out) {
  DCHECK(caller_task_runner_->BelongsToCurrentThread());
  DCHECK(delegate_.get() == NULL);

  delegate_ = delegate;

  // Create an IPC channel to communicate with the network process.
  bool result = CreateConnectedIpcChannel(io_task_runner_,
                                          this,
                                          &desktop_pipe_,
                                          &network_channel_);
  *desktop_pipe_out = desktop_pipe_;
  return result;
}

void DesktopSessionAgent::Stop() {
  DCHECK(caller_task_runner_->BelongsToCurrentThread());

  delegate_.reset();

  // Make sure the channel is closed.
  network_channel_.reset();

  if (started_) {
    started_ = false;

    // Ignore any further callbacks.
    control_factory_.InvalidateWeakPtrs();
    client_jid_.clear();

    remote_input_filter_.reset();

    // Ensure that any pressed keys or buttons are released.
    input_tracker_->ReleaseAll();
    input_tracker_.reset();

    desktop_environment_.reset();
    input_injector_.reset();
    screen_controls_.reset();

    // Stop the audio capturer.
    audio_capture_task_runner_->PostTask(
        FROM_HERE, base::Bind(&DesktopSessionAgent::StopAudioCapturer, this));

    // Stop the video capturer.
    video_capture_task_runner_->PostTask(
        FROM_HERE, base::Bind(&DesktopSessionAgent::StopVideoCapturer, this));
  }
}

void DesktopSessionAgent::OnCaptureFrame() {
  if (!video_capture_task_runner_->BelongsToCurrentThread()) {
    video_capture_task_runner_->PostTask(
        FROM_HERE,
        base::Bind(&DesktopSessionAgent::OnCaptureFrame, this));
    return;
  }

  // webrtc::ScreenCapturer supports a very few (currently 2) outstanding
  // capture requests. The requests are serialized on
  // |video_capture_task_runner()| task runner. If the client issues more
  // requests, pixel data in captured frames will likely be corrupted but
  // stability of webrtc::ScreenCapturer will not be affected.
  video_capturer_->Capture(webrtc::DesktopRegion());
}

void DesktopSessionAgent::OnInjectClipboardEvent(
    const std::string& serialized_event) {
  DCHECK(caller_task_runner_->BelongsToCurrentThread());

  protocol::ClipboardEvent event;
  if (!event.ParseFromString(serialized_event)) {
    LOG(ERROR) << "Failed to parse protocol::ClipboardEvent.";
    return;
  }

  // InputStub implementations must verify events themselves, so we don't need
  // verification here. This matches HostEventDispatcher.
  input_injector_->InjectClipboardEvent(event);
}

void DesktopSessionAgent::OnInjectKeyEvent(
    const std::string& serialized_event) {
  DCHECK(caller_task_runner_->BelongsToCurrentThread());

  protocol::KeyEvent event;
  if (!event.ParseFromString(serialized_event)) {
    LOG(ERROR) << "Failed to parse protocol::KeyEvent.";
    return;
  }

  // InputStub implementations must verify events themselves, so we need only
  // basic verification here. This matches HostEventDispatcher.
  if (!event.has_usb_keycode() || !event.has_pressed()) {
    LOG(ERROR) << "Received invalid key event.";
    return;
  }

  remote_input_filter_->InjectKeyEvent(event);
}

void DesktopSessionAgent::OnInjectTextEvent(
    const std::string& serialized_event) {
  DCHECK(caller_task_runner_->BelongsToCurrentThread());

  protocol::TextEvent event;
  if (!event.ParseFromString(serialized_event)) {
    LOG(ERROR) << "Failed to parse protocol::TextEvent.";
    return;
  }

  // InputStub implementations must verify events themselves, so we need only
  // basic verification here. This matches HostEventDispatcher.
  if (!event.has_text()) {
    LOG(ERROR) << "Received invalid TextEvent.";
    return;
  }

  remote_input_filter_->InjectTextEvent(event);
}

void DesktopSessionAgent::OnInjectMouseEvent(
    const std::string& serialized_event) {
  DCHECK(caller_task_runner_->BelongsToCurrentThread());

  protocol::MouseEvent event;
  if (!event.ParseFromString(serialized_event)) {
    LOG(ERROR) << "Failed to parse protocol::MouseEvent.";
    return;
  }

  // InputStub implementations must verify events themselves, so we don't need
  // verification here. This matches HostEventDispatcher.
  remote_input_filter_->InjectMouseEvent(event);
}

void DesktopSessionAgent::SetScreenResolution(
    const ScreenResolution& resolution) {
  DCHECK(caller_task_runner_->BelongsToCurrentThread());

  if (screen_controls_ && resolution.IsEmpty())
    screen_controls_->SetScreenResolution(resolution);
}

void DesktopSessionAgent::SendToNetwork(IPC::Message* message) {
  if (!caller_task_runner_->BelongsToCurrentThread()) {
    caller_task_runner_->PostTask(
        FROM_HERE,
        base::Bind(&DesktopSessionAgent::SendToNetwork, this, message));
    return;
  }

  if (network_channel_) {
    network_channel_->Send(message);
  } else {
    delete message;
  }
}

void DesktopSessionAgent::StartAudioCapturer() {
  DCHECK(audio_capture_task_runner_->BelongsToCurrentThread());

  if (audio_capturer_) {
    audio_capturer_->Start(base::Bind(&DesktopSessionAgent::ProcessAudioPacket,
                                      this));
  }
}

void DesktopSessionAgent::StopAudioCapturer() {
  DCHECK(audio_capture_task_runner_->BelongsToCurrentThread());

  audio_capturer_.reset();
}

void DesktopSessionAgent::StartVideoCapturer() {
  DCHECK(video_capture_task_runner_->BelongsToCurrentThread());

  if (video_capturer_) {
    video_capturer_->SetMouseShapeObserver(this);
    video_capturer_->Start(this);
  }
}

void DesktopSessionAgent::StopVideoCapturer() {
  DCHECK(video_capture_task_runner_->BelongsToCurrentThread());

  video_capturer_.reset();
  last_frame_.reset();

  // Video capturer must delete all buffers.
  DCHECK_EQ(shared_buffers_, 0);
}

void DesktopSessionAgent::OnSharedBufferDeleted(int id) {
  DCHECK(video_capture_task_runner_->BelongsToCurrentThread());
  DCHECK(id != 0);

  shared_buffers_--;
  DCHECK_GE(shared_buffers_, 0);
  SendToNetwork(new ChromotingDesktopNetworkMsg_ReleaseSharedBuffer(id));
}

void DesktopSessionAgent::CloseDesktopPipeHandle() {
  if (!(desktop_pipe_ == IPC::InvalidPlatformFileForTransit())) {
#if defined(OS_WIN)
    base::ClosePlatformFile(desktop_pipe_);
#elif defined(OS_POSIX)
    base::ClosePlatformFile(desktop_pipe_.fd);
#else  // !defined(OS_POSIX)
#error Unsupported platform.
#endif  // !defined(OS_POSIX)

    desktop_pipe_ = IPC::InvalidPlatformFileForTransit();
  }
}

}  // namespace remoting

/* [<][>][^][v][top][bottom][index][help] */