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