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