- add sources.
[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   base::win::ScopedHandle pipe;
234   if (!DuplicateHandle(desktop_process_, desktop_pipe, GetCurrentProcess(),
235                        pipe.Receive(), 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
244   IPC::ChannelHandle desktop_channel_handle(pipe);
245
246 #elif defined(OS_POSIX)
247   // On posix: |desktop_pipe| is a valid file descriptor.
248   DCHECK(desktop_pipe.auto_close);
249
250   IPC::ChannelHandle desktop_channel_handle(std::string(), desktop_pipe);
251
252 #else
253 #error Unsupported platform.
254 #endif
255
256   // Connect to the desktop process.
257   desktop_channel_.reset(new IPC::ChannelProxy(desktop_channel_handle,
258                                                IPC::Channel::MODE_CLIENT,
259                                                this,
260                                                io_task_runner_.get()));
261
262   // Pass ID of the client (which is authenticated at this point) to the desktop
263   // session agent and start the agent.
264   SendToDesktop(new ChromotingNetworkDesktopMsg_StartSessionAgent(
265       client_session_control_->client_jid(),
266       screen_resolution_,
267       virtual_terminal_));
268
269   return true;
270 }
271
272 void DesktopSessionProxy::DetachFromDesktop() {
273   DCHECK(caller_task_runner_->BelongsToCurrentThread());
274
275   desktop_channel_.reset();
276
277   if (desktop_process_ != base::kNullProcessHandle) {
278     base::CloseProcessHandle(desktop_process_);
279     desktop_process_ = base::kNullProcessHandle;
280   }
281
282   shared_buffers_.clear();
283
284   // Generate fake responses to keep the video capturer in sync.
285   while (pending_capture_frame_requests_) {
286     --pending_capture_frame_requests_;
287     PostCaptureCompleted(scoped_ptr<webrtc::DesktopFrame>());
288   }
289 }
290
291 void DesktopSessionProxy::SetAudioCapturer(
292     const base::WeakPtr<IpcAudioCapturer>& audio_capturer) {
293   DCHECK(audio_capture_task_runner_->BelongsToCurrentThread());
294
295   audio_capturer_ = audio_capturer;
296 }
297
298 void DesktopSessionProxy::CaptureFrame() {
299   if (!caller_task_runner_->BelongsToCurrentThread()) {
300     caller_task_runner_->PostTask(
301         FROM_HERE, base::Bind(&DesktopSessionProxy::CaptureFrame, this));
302     return;
303   }
304
305   if (desktop_channel_) {
306     ++pending_capture_frame_requests_;
307     SendToDesktop(new ChromotingNetworkDesktopMsg_CaptureFrame());
308   } else {
309     PostCaptureCompleted(scoped_ptr<webrtc::DesktopFrame>());
310   }
311 }
312
313 void DesktopSessionProxy::SetVideoCapturer(
314     const base::WeakPtr<IpcVideoFrameCapturer> video_capturer) {
315   DCHECK(video_capture_task_runner_->BelongsToCurrentThread());
316
317   video_capturer_ = video_capturer;
318 }
319
320 void DesktopSessionProxy::DisconnectSession() {
321   DCHECK(caller_task_runner_->BelongsToCurrentThread());
322
323   // Disconnect the client session if it hasn't been disconnected yet.
324   if (client_session_control_.get())
325     client_session_control_->DisconnectSession();
326 }
327
328 void DesktopSessionProxy::InjectClipboardEvent(
329     const protocol::ClipboardEvent& event) {
330   DCHECK(caller_task_runner_->BelongsToCurrentThread());
331
332   std::string serialized_event;
333   if (!event.SerializeToString(&serialized_event)) {
334     LOG(ERROR) << "Failed to serialize protocol::ClipboardEvent.";
335     return;
336   }
337
338   SendToDesktop(
339       new ChromotingNetworkDesktopMsg_InjectClipboardEvent(serialized_event));
340 }
341
342 void DesktopSessionProxy::InjectKeyEvent(const protocol::KeyEvent& event) {
343   DCHECK(caller_task_runner_->BelongsToCurrentThread());
344
345   std::string serialized_event;
346   if (!event.SerializeToString(&serialized_event)) {
347     LOG(ERROR) << "Failed to serialize protocol::KeyEvent.";
348     return;
349   }
350
351   SendToDesktop(
352       new ChromotingNetworkDesktopMsg_InjectKeyEvent(serialized_event));
353 }
354
355 void DesktopSessionProxy::InjectMouseEvent(const protocol::MouseEvent& event) {
356   DCHECK(caller_task_runner_->BelongsToCurrentThread());
357
358   std::string serialized_event;
359   if (!event.SerializeToString(&serialized_event)) {
360     LOG(ERROR) << "Failed to serialize protocol::MouseEvent.";
361     return;
362   }
363
364   SendToDesktop(
365       new ChromotingNetworkDesktopMsg_InjectMouseEvent(serialized_event));
366 }
367
368 void DesktopSessionProxy::StartInputInjector(
369     scoped_ptr<protocol::ClipboardStub> client_clipboard) {
370   DCHECK(caller_task_runner_->BelongsToCurrentThread());
371
372   client_clipboard_ = client_clipboard.Pass();
373 }
374
375 void DesktopSessionProxy::SetScreenResolution(
376     const ScreenResolution& resolution) {
377   DCHECK(caller_task_runner_->BelongsToCurrentThread());
378
379   if (resolution.IsEmpty())
380     return;
381
382   screen_resolution_ = resolution;
383
384   // Connect to the desktop session if it is not done yet.
385   if (!is_desktop_session_connected_) {
386     is_desktop_session_connected_ = true;
387     if (desktop_session_connector_.get()) {
388       desktop_session_connector_->ConnectTerminal(
389           this, screen_resolution_, virtual_terminal_);
390     }
391     return;
392   }
393
394   // Pass the client's resolution to both daemon and desktop session agent.
395   // Depending on the session kind the screen resolution can be set by either
396   // the daemon (for example RDP sessions on Windows) or by the desktop session
397   // agent (when sharing the physical console).
398   if (desktop_session_connector_.get())
399     desktop_session_connector_->SetScreenResolution(this, screen_resolution_);
400   SendToDesktop(
401       new ChromotingNetworkDesktopMsg_SetScreenResolution(screen_resolution_));
402 }
403
404 DesktopSessionProxy::~DesktopSessionProxy() {
405   DCHECK(caller_task_runner_->BelongsToCurrentThread());
406
407   if (desktop_session_connector_.get() && is_desktop_session_connected_)
408     desktop_session_connector_->DisconnectTerminal(this);
409
410   if (desktop_process_ != base::kNullProcessHandle) {
411     base::CloseProcessHandle(desktop_process_);
412     desktop_process_ = base::kNullProcessHandle;
413   }
414 }
415
416 scoped_refptr<DesktopSessionProxy::IpcSharedBufferCore>
417 DesktopSessionProxy::GetSharedBufferCore(int id) {
418   DCHECK(caller_task_runner_->BelongsToCurrentThread());
419
420   SharedBuffers::const_iterator i = shared_buffers_.find(id);
421   if (i != shared_buffers_.end()) {
422     return i->second;
423   } else {
424     LOG(ERROR) << "Failed to find the shared buffer " << id;
425     return NULL;
426   }
427 }
428
429 void DesktopSessionProxy::OnAudioPacket(const std::string& serialized_packet) {
430   DCHECK(caller_task_runner_->BelongsToCurrentThread());
431
432   // Parse a serialized audio packet. No further validation is done since
433   // the message was sent by more privileged process.
434   scoped_ptr<AudioPacket> packet(new AudioPacket());
435   if (!packet->ParseFromString(serialized_packet)) {
436     LOG(ERROR) << "Failed to parse AudioPacket.";
437     return;
438   }
439
440   // Pass a captured audio packet to |audio_capturer_|.
441   audio_capture_task_runner_->PostTask(
442       FROM_HERE, base::Bind(&IpcAudioCapturer::OnAudioPacket, audio_capturer_,
443                             base::Passed(&packet)));
444 }
445
446 void DesktopSessionProxy::OnCreateSharedBuffer(
447     int id,
448     IPC::PlatformFileForTransit handle,
449     uint32 size) {
450   DCHECK(caller_task_runner_->BelongsToCurrentThread());
451
452   scoped_refptr<IpcSharedBufferCore> shared_buffer =
453       new IpcSharedBufferCore(id, handle, desktop_process_, size);
454
455   if (shared_buffer->memory() != NULL &&
456       !shared_buffers_.insert(std::make_pair(id, shared_buffer)).second) {
457     LOG(ERROR) << "Duplicate shared buffer id " << id << " encountered";
458   }
459 }
460
461 void DesktopSessionProxy::OnReleaseSharedBuffer(int id) {
462   DCHECK(caller_task_runner_->BelongsToCurrentThread());
463
464   // Drop the cached reference to the buffer.
465   shared_buffers_.erase(id);
466 }
467
468 void DesktopSessionProxy::OnCaptureCompleted(
469     const SerializedDesktopFrame& serialized_frame) {
470   DCHECK(caller_task_runner_->BelongsToCurrentThread());
471
472   // Assume that |serialized_frame| is well-formed because it was received from
473   // a more privileged process.
474   scoped_refptr<IpcSharedBufferCore> shared_buffer_core =
475       GetSharedBufferCore(serialized_frame.shared_buffer_id);
476   CHECK(shared_buffer_core.get());
477
478   scoped_ptr<webrtc::DesktopFrame> frame(
479       new webrtc::SharedMemoryDesktopFrame(
480           serialized_frame.dimensions, serialized_frame.bytes_per_row,
481           new IpcSharedBuffer(shared_buffer_core)));
482   frame->set_capture_time_ms(serialized_frame.capture_time_ms);
483   frame->set_dpi(serialized_frame.dpi);
484
485   for (size_t i = 0; i < serialized_frame.dirty_region.size(); ++i) {
486     frame->mutable_updated_region()->AddRect(serialized_frame.dirty_region[i]);
487   }
488
489   --pending_capture_frame_requests_;
490   PostCaptureCompleted(frame.Pass());
491 }
492
493 void DesktopSessionProxy::OnCursorShapeChanged(
494     const webrtc::MouseCursorShape& cursor_shape) {
495   DCHECK(caller_task_runner_->BelongsToCurrentThread());
496   PostCursorShape(scoped_ptr<webrtc::MouseCursorShape>(
497       new webrtc::MouseCursorShape(cursor_shape)));
498 }
499
500 void DesktopSessionProxy::OnInjectClipboardEvent(
501     const std::string& serialized_event) {
502   DCHECK(caller_task_runner_->BelongsToCurrentThread());
503
504   if (client_clipboard_) {
505     protocol::ClipboardEvent event;
506     if (!event.ParseFromString(serialized_event)) {
507       LOG(ERROR) << "Failed to parse protocol::ClipboardEvent.";
508       return;
509     }
510
511     client_clipboard_->InjectClipboardEvent(event);
512   }
513 }
514
515 void DesktopSessionProxy::PostCaptureCompleted(
516     scoped_ptr<webrtc::DesktopFrame> frame) {
517   DCHECK(caller_task_runner_->BelongsToCurrentThread());
518
519   video_capture_task_runner_->PostTask(
520       FROM_HERE,
521       base::Bind(&IpcVideoFrameCapturer::OnCaptureCompleted, video_capturer_,
522                  base::Passed(&frame)));
523 }
524
525 void DesktopSessionProxy::PostCursorShape(
526     scoped_ptr<webrtc::MouseCursorShape> cursor_shape) {
527   DCHECK(caller_task_runner_->BelongsToCurrentThread());
528
529   video_capture_task_runner_->PostTask(
530       FROM_HERE,
531       base::Bind(&IpcVideoFrameCapturer::OnCursorShapeChanged, video_capturer_,
532                  base::Passed(&cursor_shape)));
533 }
534
535 void DesktopSessionProxy::SendToDesktop(IPC::Message* message) {
536   DCHECK(caller_task_runner_->BelongsToCurrentThread());
537
538   if (desktop_channel_) {
539     desktop_channel_->Send(message);
540   } else {
541     delete message;
542   }
543 }
544
545 // static
546 void DesktopSessionProxyTraits::Destruct(
547     const DesktopSessionProxy* desktop_session_proxy) {
548   desktop_session_proxy->caller_task_runner_->DeleteSoon(FROM_HERE,
549                                                          desktop_session_proxy);
550 }
551
552 }  // namespace remoting