Home | History | Annotate | Download | only in host
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "remoting/host/desktop_session_proxy.h"
      6 
      7 #include "base/compiler_specific.h"
      8 #include "base/logging.h"
      9 #include "base/process/process_handle.h"
     10 #include "base/memory/shared_memory.h"
     11 #include "base/single_thread_task_runner.h"
     12 #include "ipc/ipc_channel_proxy.h"
     13 #include "ipc/ipc_message_macros.h"
     14 #include "remoting/base/capabilities.h"
     15 #include "remoting/host/chromoting_messages.h"
     16 #include "remoting/host/client_session.h"
     17 #include "remoting/host/client_session_control.h"
     18 #include "remoting/host/desktop_session_connector.h"
     19 #include "remoting/host/ipc_audio_capturer.h"
     20 #include "remoting/host/ipc_input_injector.h"
     21 #include "remoting/host/ipc_mouse_cursor_monitor.h"
     22 #include "remoting/host/ipc_screen_controls.h"
     23 #include "remoting/host/ipc_video_frame_capturer.h"
     24 #include "remoting/proto/audio.pb.h"
     25 #include "remoting/proto/control.pb.h"
     26 #include "remoting/proto/event.pb.h"
     27 #include "third_party/webrtc/modules/desktop_capture/desktop_frame.h"
     28 #include "third_party/webrtc/modules/desktop_capture/desktop_geometry.h"
     29 #include "third_party/webrtc/modules/desktop_capture/mouse_cursor.h"
     30 #include "third_party/webrtc/modules/desktop_capture/shared_memory.h"
     31 
     32 #if defined(OS_WIN)
     33 #include "base/win/scoped_handle.h"
     34 #endif  // defined(OS_WIN)
     35 
     36 const bool kReadOnly = true;
     37 const char kSendInitialResolution[] = "sendInitialResolution";
     38 const char kRateLimitResizeRequests[] = "rateLimitResizeRequests";
     39 
     40 namespace remoting {
     41 
     42 class DesktopSessionProxy::IpcSharedBufferCore
     43     : public base::RefCountedThreadSafe<IpcSharedBufferCore> {
     44  public:
     45   IpcSharedBufferCore(int id,
     46                       base::SharedMemoryHandle handle,
     47                       base::ProcessHandle process,
     48                       size_t size)
     49       : id_(id),
     50 #if defined(OS_WIN)
     51         shared_memory_(handle, kReadOnly, process),
     52 #else  // !defined(OS_WIN)
     53         shared_memory_(handle, kReadOnly),
     54 #endif  // !defined(OS_WIN)
     55         size_(size) {
     56     if (!shared_memory_.Map(size)) {
     57       LOG(ERROR) << "Failed to map a shared buffer: id=" << id
     58 #if defined(OS_WIN)
     59                  << ", handle=" << handle
     60 #else
     61                  << ", handle.fd=" << handle.fd
     62 #endif
     63                  << ", size=" << size;
     64     }
     65   }
     66 
     67   int id() { return id_; }
     68   size_t size() { return size_; }
     69   void* memory() { return shared_memory_.memory(); }
     70   webrtc::SharedMemory::Handle handle() {
     71 #if defined(OS_WIN)
     72     return shared_memory_.handle();
     73 #else
     74     return shared_memory_.handle().fd;
     75 #endif
     76   }
     77 
     78  private:
     79   virtual ~IpcSharedBufferCore() {}
     80   friend class base::RefCountedThreadSafe<IpcSharedBufferCore>;
     81 
     82   int id_;
     83   base::SharedMemory shared_memory_;
     84   size_t size_;
     85 
     86   DISALLOW_COPY_AND_ASSIGN(IpcSharedBufferCore);
     87 };
     88 
     89 class DesktopSessionProxy::IpcSharedBuffer : public webrtc::SharedMemory {
     90  public:
     91   IpcSharedBuffer(scoped_refptr<IpcSharedBufferCore> core)
     92       : SharedMemory(core->memory(), core->size(),
     93                      core->handle(), core->id()),
     94         core_(core) {
     95   }
     96 
     97  private:
     98   scoped_refptr<IpcSharedBufferCore> core_;
     99 
    100   DISALLOW_COPY_AND_ASSIGN(IpcSharedBuffer);
    101 };
    102 
    103 DesktopSessionProxy::DesktopSessionProxy(
    104     scoped_refptr<base::SingleThreadTaskRunner> audio_capture_task_runner,
    105     scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner,
    106     scoped_refptr<base::SingleThreadTaskRunner> io_task_runner,
    107     scoped_refptr<base::SingleThreadTaskRunner> video_capture_task_runner,
    108     base::WeakPtr<ClientSessionControl> client_session_control,
    109     base::WeakPtr<DesktopSessionConnector> desktop_session_connector,
    110     bool virtual_terminal)
    111     : audio_capture_task_runner_(audio_capture_task_runner),
    112       caller_task_runner_(caller_task_runner),
    113       io_task_runner_(io_task_runner),
    114       video_capture_task_runner_(video_capture_task_runner),
    115       client_session_control_(client_session_control),
    116       desktop_session_connector_(desktop_session_connector),
    117       desktop_process_(base::kNullProcessHandle),
    118       pending_capture_frame_requests_(0),
    119       is_desktop_session_connected_(false),
    120       virtual_terminal_(virtual_terminal) {
    121   DCHECK(caller_task_runner_->BelongsToCurrentThread());
    122 }
    123 
    124 scoped_ptr<AudioCapturer> DesktopSessionProxy::CreateAudioCapturer() {
    125   DCHECK(caller_task_runner_->BelongsToCurrentThread());
    126 
    127   return scoped_ptr<AudioCapturer>(new IpcAudioCapturer(this));
    128 }
    129 
    130 scoped_ptr<InputInjector> DesktopSessionProxy::CreateInputInjector() {
    131   DCHECK(caller_task_runner_->BelongsToCurrentThread());
    132 
    133   return scoped_ptr<InputInjector>(new IpcInputInjector(this));
    134 }
    135 
    136 scoped_ptr<ScreenControls> DesktopSessionProxy::CreateScreenControls() {
    137   DCHECK(caller_task_runner_->BelongsToCurrentThread());
    138 
    139   return scoped_ptr<ScreenControls>(new IpcScreenControls(this));
    140 }
    141 
    142 scoped_ptr<webrtc::DesktopCapturer> DesktopSessionProxy::CreateVideoCapturer() {
    143   DCHECK(caller_task_runner_->BelongsToCurrentThread());
    144 
    145   return scoped_ptr<webrtc::DesktopCapturer>(new IpcVideoFrameCapturer(this));
    146 }
    147 
    148 scoped_ptr<webrtc::MouseCursorMonitor>
    149     DesktopSessionProxy::CreateMouseCursorMonitor() {
    150   return scoped_ptr<webrtc::MouseCursorMonitor>(
    151       new IpcMouseCursorMonitor(this));
    152 }
    153 
    154 std::string DesktopSessionProxy::GetCapabilities() const {
    155   std::string result = kRateLimitResizeRequests;
    156   // Ask the client to send its resolution unconditionally.
    157   if (virtual_terminal_)
    158     result = result + " " + kSendInitialResolution;
    159   return result;
    160 }
    161 
    162 void DesktopSessionProxy::SetCapabilities(const std::string& capabilities) {
    163   // Delay creation of the desktop session until the client screen resolution is
    164   // received if the desktop session requires the initial screen resolution
    165   // (when |virtual_terminal_| is true) and the client is expected to
    166   // sent its screen resolution (the 'sendInitialResolution' capability is
    167   // supported).
    168   if (virtual_terminal_ &&
    169       HasCapability(capabilities, kSendInitialResolution)) {
    170     VLOG(1) << "Waiting for the client screen resolution.";
    171     return;
    172   }
    173 
    174   // Connect to the desktop session.
    175   if (!is_desktop_session_connected_) {
    176     is_desktop_session_connected_ = true;
    177     if (desktop_session_connector_.get()) {
    178       desktop_session_connector_->ConnectTerminal(
    179           this, screen_resolution_, virtual_terminal_);
    180     }
    181   }
    182 }
    183 
    184 bool DesktopSessionProxy::OnMessageReceived(const IPC::Message& message) {
    185   DCHECK(caller_task_runner_->BelongsToCurrentThread());
    186 
    187   bool handled = true;
    188   IPC_BEGIN_MESSAGE_MAP(DesktopSessionProxy, message)
    189     IPC_MESSAGE_HANDLER(ChromotingDesktopNetworkMsg_AudioPacket,
    190                         OnAudioPacket)
    191     IPC_MESSAGE_HANDLER(ChromotingDesktopNetworkMsg_CaptureCompleted,
    192                         OnCaptureCompleted)
    193     IPC_MESSAGE_HANDLER(ChromotingDesktopNetworkMsg_MouseCursor,
    194                         OnMouseCursor)
    195     IPC_MESSAGE_HANDLER(ChromotingDesktopNetworkMsg_CreateSharedBuffer,
    196                         OnCreateSharedBuffer)
    197     IPC_MESSAGE_HANDLER(ChromotingDesktopNetworkMsg_ReleaseSharedBuffer,
    198                         OnReleaseSharedBuffer)
    199     IPC_MESSAGE_HANDLER(ChromotingDesktopNetworkMsg_InjectClipboardEvent,
    200                         OnInjectClipboardEvent)
    201     IPC_MESSAGE_HANDLER(ChromotingDesktopNetworkMsg_DisconnectSession,
    202                         DisconnectSession);
    203   IPC_END_MESSAGE_MAP()
    204 
    205   CHECK(handled) << "Received unexpected IPC type: " << message.type();
    206   return handled;
    207 }
    208 
    209 void DesktopSessionProxy::OnChannelConnected(int32 peer_pid) {
    210   DCHECK(caller_task_runner_->BelongsToCurrentThread());
    211 
    212   VLOG(1) << "IPC: network <- desktop (" << peer_pid << ")";
    213 }
    214 
    215 void DesktopSessionProxy::OnChannelError() {
    216   DCHECK(caller_task_runner_->BelongsToCurrentThread());
    217 
    218   DetachFromDesktop();
    219 }
    220 
    221 bool DesktopSessionProxy::AttachToDesktop(
    222     base::ProcessHandle desktop_process,
    223     IPC::PlatformFileForTransit desktop_pipe) {
    224   DCHECK(caller_task_runner_->BelongsToCurrentThread());
    225   DCHECK(!desktop_channel_);
    226   DCHECK_EQ(desktop_process_, base::kNullProcessHandle);
    227 
    228   // Ignore the attach notification if the client session has been disconnected
    229   // already.
    230   if (!client_session_control_.get()) {
    231     base::CloseProcessHandle(desktop_process);
    232     return false;
    233   }
    234 
    235   desktop_process_ = desktop_process;
    236 
    237 #if defined(OS_WIN)
    238   // On Windows: |desktop_process| is a valid handle, but |desktop_pipe| needs
    239   // to be duplicated from the desktop process.
    240   HANDLE temp_handle;
    241   if (!DuplicateHandle(desktop_process_, desktop_pipe, GetCurrentProcess(),
    242                        &temp_handle, 0, FALSE, DUPLICATE_SAME_ACCESS)) {
    243     PLOG(ERROR) << "Failed to duplicate the desktop-to-network pipe handle";
    244 
    245     desktop_process_ = base::kNullProcessHandle;
    246     base::CloseProcessHandle(desktop_process);
    247     return false;
    248   }
    249   base::win::ScopedHandle pipe(temp_handle);
    250 
    251   IPC::ChannelHandle desktop_channel_handle(pipe.Get());
    252 
    253 #elif defined(OS_POSIX)
    254   // On posix: |desktop_pipe| is a valid file descriptor.
    255   DCHECK(desktop_pipe.auto_close);
    256 
    257   IPC::ChannelHandle desktop_channel_handle(std::string(), desktop_pipe);
    258 
    259 #else
    260 #error Unsupported platform.
    261 #endif
    262 
    263   // Connect to the desktop process.
    264   desktop_channel_ = IPC::ChannelProxy::Create(desktop_channel_handle,
    265                                                IPC::Channel::MODE_CLIENT,
    266                                                this,
    267                                                io_task_runner_.get());
    268 
    269   // Pass ID of the client (which is authenticated at this point) to the desktop
    270   // session agent and start the agent.
    271   SendToDesktop(new ChromotingNetworkDesktopMsg_StartSessionAgent(
    272       client_session_control_->client_jid(),
    273       screen_resolution_,
    274       virtual_terminal_));
    275 
    276   return true;
    277 }
    278 
    279 void DesktopSessionProxy::DetachFromDesktop() {
    280   DCHECK(caller_task_runner_->BelongsToCurrentThread());
    281 
    282   desktop_channel_.reset();
    283 
    284   if (desktop_process_ != base::kNullProcessHandle) {
    285     base::CloseProcessHandle(desktop_process_);
    286     desktop_process_ = base::kNullProcessHandle;
    287   }
    288 
    289   shared_buffers_.clear();
    290 
    291   // Generate fake responses to keep the video capturer in sync.
    292   while (pending_capture_frame_requests_) {
    293     --pending_capture_frame_requests_;
    294     PostCaptureCompleted(scoped_ptr<webrtc::DesktopFrame>());
    295   }
    296 }
    297 
    298 void DesktopSessionProxy::SetAudioCapturer(
    299     const base::WeakPtr<IpcAudioCapturer>& audio_capturer) {
    300   DCHECK(audio_capture_task_runner_->BelongsToCurrentThread());
    301 
    302   audio_capturer_ = audio_capturer;
    303 }
    304 
    305 void DesktopSessionProxy::CaptureFrame() {
    306   if (!caller_task_runner_->BelongsToCurrentThread()) {
    307     caller_task_runner_->PostTask(
    308         FROM_HERE, base::Bind(&DesktopSessionProxy::CaptureFrame, this));
    309     return;
    310   }
    311 
    312   if (desktop_channel_) {
    313     ++pending_capture_frame_requests_;
    314     SendToDesktop(new ChromotingNetworkDesktopMsg_CaptureFrame());
    315   } else {
    316     PostCaptureCompleted(scoped_ptr<webrtc::DesktopFrame>());
    317   }
    318 }
    319 
    320 void DesktopSessionProxy::SetVideoCapturer(
    321     const base::WeakPtr<IpcVideoFrameCapturer> video_capturer) {
    322   DCHECK(video_capture_task_runner_->BelongsToCurrentThread());
    323 
    324   video_capturer_ = video_capturer;
    325 }
    326 
    327 void DesktopSessionProxy::SetMouseCursorMonitor(
    328     const base::WeakPtr<IpcMouseCursorMonitor>& mouse_cursor_monitor) {
    329   DCHECK(video_capture_task_runner_->BelongsToCurrentThread());
    330 
    331   mouse_cursor_monitor_ = mouse_cursor_monitor;
    332 }
    333 
    334 void DesktopSessionProxy::DisconnectSession() {
    335   DCHECK(caller_task_runner_->BelongsToCurrentThread());
    336 
    337   // Disconnect the client session if it hasn't been disconnected yet.
    338   if (client_session_control_.get())
    339     client_session_control_->DisconnectSession();
    340 }
    341 
    342 void DesktopSessionProxy::InjectClipboardEvent(
    343     const protocol::ClipboardEvent& event) {
    344   DCHECK(caller_task_runner_->BelongsToCurrentThread());
    345 
    346   std::string serialized_event;
    347   if (!event.SerializeToString(&serialized_event)) {
    348     LOG(ERROR) << "Failed to serialize protocol::ClipboardEvent.";
    349     return;
    350   }
    351 
    352   SendToDesktop(
    353       new ChromotingNetworkDesktopMsg_InjectClipboardEvent(serialized_event));
    354 }
    355 
    356 void DesktopSessionProxy::InjectKeyEvent(const protocol::KeyEvent& event) {
    357   DCHECK(caller_task_runner_->BelongsToCurrentThread());
    358 
    359   std::string serialized_event;
    360   if (!event.SerializeToString(&serialized_event)) {
    361     LOG(ERROR) << "Failed to serialize protocol::KeyEvent.";
    362     return;
    363   }
    364 
    365   SendToDesktop(
    366       new ChromotingNetworkDesktopMsg_InjectKeyEvent(serialized_event));
    367 }
    368 
    369 void DesktopSessionProxy::InjectTextEvent(const protocol::TextEvent& event) {
    370   DCHECK(caller_task_runner_->BelongsToCurrentThread());
    371 
    372   std::string serialized_event;
    373   if (!event.SerializeToString(&serialized_event)) {
    374     LOG(ERROR) << "Failed to serialize protocol::TextEvent.";
    375     return;
    376   }
    377 
    378   SendToDesktop(
    379       new ChromotingNetworkDesktopMsg_InjectTextEvent(serialized_event));
    380 }
    381 
    382 void DesktopSessionProxy::InjectMouseEvent(const protocol::MouseEvent& event) {
    383   DCHECK(caller_task_runner_->BelongsToCurrentThread());
    384 
    385   std::string serialized_event;
    386   if (!event.SerializeToString(&serialized_event)) {
    387     LOG(ERROR) << "Failed to serialize protocol::MouseEvent.";
    388     return;
    389   }
    390 
    391   SendToDesktop(
    392       new ChromotingNetworkDesktopMsg_InjectMouseEvent(serialized_event));
    393 }
    394 
    395 void DesktopSessionProxy::StartInputInjector(
    396     scoped_ptr<protocol::ClipboardStub> client_clipboard) {
    397   DCHECK(caller_task_runner_->BelongsToCurrentThread());
    398 
    399   client_clipboard_ = client_clipboard.Pass();
    400 }
    401 
    402 void DesktopSessionProxy::SetScreenResolution(
    403     const ScreenResolution& resolution) {
    404   DCHECK(caller_task_runner_->BelongsToCurrentThread());
    405 
    406   if (resolution.IsEmpty())
    407     return;
    408 
    409   screen_resolution_ = resolution;
    410 
    411   // Connect to the desktop session if it is not done yet.
    412   if (!is_desktop_session_connected_) {
    413     is_desktop_session_connected_ = true;
    414     if (desktop_session_connector_.get()) {
    415       desktop_session_connector_->ConnectTerminal(
    416           this, screen_resolution_, virtual_terminal_);
    417     }
    418     return;
    419   }
    420 
    421   // Pass the client's resolution to both daemon and desktop session agent.
    422   // Depending on the session kind the screen resolution can be set by either
    423   // the daemon (for example RDP sessions on Windows) or by the desktop session
    424   // agent (when sharing the physical console).
    425   if (desktop_session_connector_.get())
    426     desktop_session_connector_->SetScreenResolution(this, screen_resolution_);
    427   SendToDesktop(
    428       new ChromotingNetworkDesktopMsg_SetScreenResolution(screen_resolution_));
    429 }
    430 
    431 DesktopSessionProxy::~DesktopSessionProxy() {
    432   DCHECK(caller_task_runner_->BelongsToCurrentThread());
    433 
    434   if (desktop_session_connector_.get() && is_desktop_session_connected_)
    435     desktop_session_connector_->DisconnectTerminal(this);
    436 
    437   if (desktop_process_ != base::kNullProcessHandle) {
    438     base::CloseProcessHandle(desktop_process_);
    439     desktop_process_ = base::kNullProcessHandle;
    440   }
    441 }
    442 
    443 scoped_refptr<DesktopSessionProxy::IpcSharedBufferCore>
    444 DesktopSessionProxy::GetSharedBufferCore(int id) {
    445   DCHECK(caller_task_runner_->BelongsToCurrentThread());
    446 
    447   SharedBuffers::const_iterator i = shared_buffers_.find(id);
    448   if (i != shared_buffers_.end()) {
    449     return i->second;
    450   } else {
    451     LOG(ERROR) << "Failed to find the shared buffer " << id;
    452     return NULL;
    453   }
    454 }
    455 
    456 void DesktopSessionProxy::OnAudioPacket(const std::string& serialized_packet) {
    457   DCHECK(caller_task_runner_->BelongsToCurrentThread());
    458 
    459   // Parse a serialized audio packet. No further validation is done since
    460   // the message was sent by more privileged process.
    461   scoped_ptr<AudioPacket> packet(new AudioPacket());
    462   if (!packet->ParseFromString(serialized_packet)) {
    463     LOG(ERROR) << "Failed to parse AudioPacket.";
    464     return;
    465   }
    466 
    467   // Pass a captured audio packet to |audio_capturer_|.
    468   audio_capture_task_runner_->PostTask(
    469       FROM_HERE, base::Bind(&IpcAudioCapturer::OnAudioPacket, audio_capturer_,
    470                             base::Passed(&packet)));
    471 }
    472 
    473 void DesktopSessionProxy::OnCreateSharedBuffer(
    474     int id,
    475     IPC::PlatformFileForTransit handle,
    476     uint32 size) {
    477   DCHECK(caller_task_runner_->BelongsToCurrentThread());
    478 
    479   scoped_refptr<IpcSharedBufferCore> shared_buffer =
    480       new IpcSharedBufferCore(id, handle, desktop_process_, size);
    481 
    482   if (shared_buffer->memory() != NULL &&
    483       !shared_buffers_.insert(std::make_pair(id, shared_buffer)).second) {
    484     LOG(ERROR) << "Duplicate shared buffer id " << id << " encountered";
    485   }
    486 }
    487 
    488 void DesktopSessionProxy::OnReleaseSharedBuffer(int id) {
    489   DCHECK(caller_task_runner_->BelongsToCurrentThread());
    490 
    491   // Drop the cached reference to the buffer.
    492   shared_buffers_.erase(id);
    493 }
    494 
    495 void DesktopSessionProxy::OnCaptureCompleted(
    496     const SerializedDesktopFrame& serialized_frame) {
    497   DCHECK(caller_task_runner_->BelongsToCurrentThread());
    498 
    499   // Assume that |serialized_frame| is well-formed because it was received from
    500   // a more privileged process.
    501   scoped_refptr<IpcSharedBufferCore> shared_buffer_core =
    502       GetSharedBufferCore(serialized_frame.shared_buffer_id);
    503   CHECK(shared_buffer_core.get());
    504 
    505   scoped_ptr<webrtc::DesktopFrame> frame(
    506       new webrtc::SharedMemoryDesktopFrame(
    507           serialized_frame.dimensions, serialized_frame.bytes_per_row,
    508           new IpcSharedBuffer(shared_buffer_core)));
    509   frame->set_capture_time_ms(serialized_frame.capture_time_ms);
    510   frame->set_dpi(serialized_frame.dpi);
    511 
    512   for (size_t i = 0; i < serialized_frame.dirty_region.size(); ++i) {
    513     frame->mutable_updated_region()->AddRect(serialized_frame.dirty_region[i]);
    514   }
    515 
    516   --pending_capture_frame_requests_;
    517   PostCaptureCompleted(frame.Pass());
    518 }
    519 
    520 void DesktopSessionProxy::OnMouseCursor(
    521     const webrtc::MouseCursor& mouse_cursor) {
    522   DCHECK(caller_task_runner_->BelongsToCurrentThread());
    523   scoped_ptr<webrtc::MouseCursor> cursor(
    524       webrtc::MouseCursor::CopyOf(mouse_cursor));
    525   PostMouseCursor(cursor.Pass());
    526 }
    527 
    528 void DesktopSessionProxy::OnInjectClipboardEvent(
    529     const std::string& serialized_event) {
    530   DCHECK(caller_task_runner_->BelongsToCurrentThread());
    531 
    532   if (client_clipboard_) {
    533     protocol::ClipboardEvent event;
    534     if (!event.ParseFromString(serialized_event)) {
    535       LOG(ERROR) << "Failed to parse protocol::ClipboardEvent.";
    536       return;
    537     }
    538 
    539     client_clipboard_->InjectClipboardEvent(event);
    540   }
    541 }
    542 
    543 void DesktopSessionProxy::PostCaptureCompleted(
    544     scoped_ptr<webrtc::DesktopFrame> frame) {
    545   DCHECK(caller_task_runner_->BelongsToCurrentThread());
    546 
    547   video_capture_task_runner_->PostTask(
    548       FROM_HERE,
    549       base::Bind(&IpcVideoFrameCapturer::OnCaptureCompleted, video_capturer_,
    550                  base::Passed(&frame)));
    551 }
    552 
    553 void DesktopSessionProxy::PostMouseCursor(
    554     scoped_ptr<webrtc::MouseCursor> mouse_cursor) {
    555   DCHECK(caller_task_runner_->BelongsToCurrentThread());
    556 
    557   video_capture_task_runner_->PostTask(
    558       FROM_HERE,
    559       base::Bind(&IpcMouseCursorMonitor::OnMouseCursor, mouse_cursor_monitor_,
    560                  base::Passed(&mouse_cursor)));
    561 }
    562 
    563 void DesktopSessionProxy::SendToDesktop(IPC::Message* message) {
    564   DCHECK(caller_task_runner_->BelongsToCurrentThread());
    565 
    566   if (desktop_channel_) {
    567     desktop_channel_->Send(message);
    568   } else {
    569     delete message;
    570   }
    571 }
    572 
    573 // static
    574 void DesktopSessionProxyTraits::Destruct(
    575     const DesktopSessionProxy* desktop_session_proxy) {
    576   desktop_session_proxy->caller_task_runner_->DeleteSoon(FROM_HERE,
    577                                                          desktop_session_proxy);
    578 }
    579 
    580 }  // namespace remoting
    581