desktop_session_proxy.cc revision effb81e5f8246d0db0270817048dc992db66e9fb
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
35const bool kReadOnly = true;
36const char kSendInitialResolution[] = "sendInitialResolution";
37const char kRateLimitResizeRequests[] = "rateLimitResizeRequests";
38
39namespace remoting {
40
41class 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
88class 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
102DesktopSessionProxy::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
123scoped_ptr<AudioCapturer> DesktopSessionProxy::CreateAudioCapturer() {
124  DCHECK(caller_task_runner_->BelongsToCurrentThread());
125
126  return scoped_ptr<AudioCapturer>(new IpcAudioCapturer(this));
127}
128
129scoped_ptr<InputInjector> DesktopSessionProxy::CreateInputInjector() {
130  DCHECK(caller_task_runner_->BelongsToCurrentThread());
131
132  return scoped_ptr<InputInjector>(new IpcInputInjector(this));
133}
134
135scoped_ptr<ScreenControls> DesktopSessionProxy::CreateScreenControls() {
136  DCHECK(caller_task_runner_->BelongsToCurrentThread());
137
138  return scoped_ptr<ScreenControls>(new IpcScreenControls(this));
139}
140
141scoped_ptr<webrtc::ScreenCapturer> DesktopSessionProxy::CreateVideoCapturer() {
142  DCHECK(caller_task_runner_->BelongsToCurrentThread());
143
144  return scoped_ptr<webrtc::ScreenCapturer>(new IpcVideoFrameCapturer(this));
145}
146
147std::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
155void 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
177bool 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
202void DesktopSessionProxy::OnChannelConnected(int32 peer_pid) {
203  DCHECK(caller_task_runner_->BelongsToCurrentThread());
204
205  VLOG(1) << "IPC: network <- desktop (" << peer_pid << ")";
206}
207
208void DesktopSessionProxy::OnChannelError() {
209  DCHECK(caller_task_runner_->BelongsToCurrentThread());
210
211  DetachFromDesktop();
212}
213
214bool 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
273void 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
292void DesktopSessionProxy::SetAudioCapturer(
293    const base::WeakPtr<IpcAudioCapturer>& audio_capturer) {
294  DCHECK(audio_capture_task_runner_->BelongsToCurrentThread());
295
296  audio_capturer_ = audio_capturer;
297}
298
299void 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
314void DesktopSessionProxy::SetVideoCapturer(
315    const base::WeakPtr<IpcVideoFrameCapturer> video_capturer) {
316  DCHECK(video_capture_task_runner_->BelongsToCurrentThread());
317
318  video_capturer_ = video_capturer;
319}
320
321void 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
329void 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
343void 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
356void 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
369void 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
382void DesktopSessionProxy::StartInputInjector(
383    scoped_ptr<protocol::ClipboardStub> client_clipboard) {
384  DCHECK(caller_task_runner_->BelongsToCurrentThread());
385
386  client_clipboard_ = client_clipboard.Pass();
387}
388
389void 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
418DesktopSessionProxy::~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
430scoped_refptr<DesktopSessionProxy::IpcSharedBufferCore>
431DesktopSessionProxy::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
443void 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
460void 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
475void 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
482void 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
507void 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
514void 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
529void 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
539void 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
549void 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
560void 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
567