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_agent.h"
6
7#include "base/files/file_util.h"
8#include "base/logging.h"
9#include "base/memory/shared_memory.h"
10#include "ipc/ipc_channel_proxy.h"
11#include "ipc/ipc_message.h"
12#include "ipc/ipc_message_macros.h"
13#include "remoting/base/auto_thread_task_runner.h"
14#include "remoting/base/constants.h"
15#include "remoting/host/audio_capturer.h"
16#include "remoting/host/chromoting_messages.h"
17#include "remoting/host/desktop_environment.h"
18#include "remoting/host/input_injector.h"
19#include "remoting/host/ipc_util.h"
20#include "remoting/host/remote_input_filter.h"
21#include "remoting/host/screen_controls.h"
22#include "remoting/host/screen_resolution.h"
23#include "remoting/proto/audio.pb.h"
24#include "remoting/proto/control.pb.h"
25#include "remoting/proto/event.pb.h"
26#include "remoting/protocol/clipboard_stub.h"
27#include "remoting/protocol/input_event_tracker.h"
28#include "third_party/webrtc/modules/desktop_capture/desktop_frame.h"
29#include "third_party/webrtc/modules/desktop_capture/desktop_geometry.h"
30#include "third_party/webrtc/modules/desktop_capture/mouse_cursor.h"
31#include "third_party/webrtc/modules/desktop_capture/shared_memory.h"
32
33namespace remoting {
34
35namespace {
36
37// Routes local clipboard events though the IPC channel to the network process.
38class DesktopSesssionClipboardStub : public protocol::ClipboardStub {
39 public:
40  explicit DesktopSesssionClipboardStub(
41      scoped_refptr<DesktopSessionAgent> desktop_session_agent);
42  virtual ~DesktopSesssionClipboardStub();
43
44  // protocol::ClipboardStub implementation.
45  virtual void InjectClipboardEvent(
46      const protocol::ClipboardEvent& event) OVERRIDE;
47
48 private:
49  scoped_refptr<DesktopSessionAgent> desktop_session_agent_;
50
51  DISALLOW_COPY_AND_ASSIGN(DesktopSesssionClipboardStub);
52};
53
54DesktopSesssionClipboardStub::DesktopSesssionClipboardStub(
55    scoped_refptr<DesktopSessionAgent> desktop_session_agent)
56    : desktop_session_agent_(desktop_session_agent) {
57}
58
59DesktopSesssionClipboardStub::~DesktopSesssionClipboardStub() {
60}
61
62void DesktopSesssionClipboardStub::InjectClipboardEvent(
63    const protocol::ClipboardEvent& event) {
64  desktop_session_agent_->InjectClipboardEvent(event);
65}
66
67}  // namespace
68
69// webrtc::SharedMemory implementation that notifies creating
70// DesktopSessionAgent when it's deleted.
71class DesktopSessionAgent::SharedBuffer : public webrtc::SharedMemory {
72 public:
73  static scoped_ptr<SharedBuffer> Create(DesktopSessionAgent* agent,
74                                         size_t size,
75                                         int id) {
76    scoped_ptr<base::SharedMemory> memory(new base::SharedMemory());
77    if (!memory->CreateAndMapAnonymous(size))
78      return scoped_ptr<SharedBuffer>();
79    return scoped_ptr<SharedBuffer>(
80        new SharedBuffer(agent, memory.Pass(), size, id));
81  }
82
83  virtual ~SharedBuffer() {
84    agent_->OnSharedBufferDeleted(id());
85  }
86
87 private:
88  SharedBuffer(DesktopSessionAgent* agent,
89               scoped_ptr<base::SharedMemory> memory,
90               size_t size,
91               int id)
92      : SharedMemory(memory->memory(), size,
93#if defined(OS_WIN)
94                     memory->handle(),
95#else
96                     memory->handle().fd,
97#endif
98                     id),
99        agent_(agent),
100        shared_memory_(memory.Pass()) {
101  }
102
103  DesktopSessionAgent* agent_;
104  scoped_ptr<base::SharedMemory> shared_memory_;
105
106  DISALLOW_COPY_AND_ASSIGN(SharedBuffer);
107};
108
109DesktopSessionAgent::Delegate::~Delegate() {
110}
111
112DesktopSessionAgent::DesktopSessionAgent(
113    scoped_refptr<AutoThreadTaskRunner> audio_capture_task_runner,
114    scoped_refptr<AutoThreadTaskRunner> caller_task_runner,
115    scoped_refptr<AutoThreadTaskRunner> input_task_runner,
116    scoped_refptr<AutoThreadTaskRunner> io_task_runner,
117    scoped_refptr<AutoThreadTaskRunner> video_capture_task_runner)
118    : audio_capture_task_runner_(audio_capture_task_runner),
119      caller_task_runner_(caller_task_runner),
120      input_task_runner_(input_task_runner),
121      io_task_runner_(io_task_runner),
122      video_capture_task_runner_(video_capture_task_runner),
123      next_shared_buffer_id_(1),
124      shared_buffers_(0),
125      started_(false),
126      weak_factory_(this) {
127  DCHECK(caller_task_runner_->BelongsToCurrentThread());
128}
129
130bool DesktopSessionAgent::OnMessageReceived(const IPC::Message& message) {
131  DCHECK(caller_task_runner_->BelongsToCurrentThread());
132
133  bool handled = true;
134  if (started_) {
135    IPC_BEGIN_MESSAGE_MAP(DesktopSessionAgent, message)
136      IPC_MESSAGE_HANDLER(ChromotingNetworkDesktopMsg_CaptureFrame,
137                          OnCaptureFrame)
138      IPC_MESSAGE_HANDLER(ChromotingNetworkDesktopMsg_InjectClipboardEvent,
139                          OnInjectClipboardEvent)
140      IPC_MESSAGE_HANDLER(ChromotingNetworkDesktopMsg_InjectKeyEvent,
141                          OnInjectKeyEvent)
142      IPC_MESSAGE_HANDLER(ChromotingNetworkDesktopMsg_InjectTextEvent,
143                          OnInjectTextEvent)
144      IPC_MESSAGE_HANDLER(ChromotingNetworkDesktopMsg_InjectMouseEvent,
145                          OnInjectMouseEvent)
146      IPC_MESSAGE_HANDLER(ChromotingNetworkDesktopMsg_SetScreenResolution,
147                          SetScreenResolution)
148      IPC_MESSAGE_UNHANDLED(handled = false)
149    IPC_END_MESSAGE_MAP()
150  } else {
151    IPC_BEGIN_MESSAGE_MAP(DesktopSessionAgent, message)
152      IPC_MESSAGE_HANDLER(ChromotingNetworkDesktopMsg_StartSessionAgent,
153                          OnStartSessionAgent)
154      IPC_MESSAGE_UNHANDLED(handled = false)
155    IPC_END_MESSAGE_MAP()
156  }
157
158  CHECK(handled) << "Received unexpected IPC type: " << message.type();
159  return handled;
160}
161
162void DesktopSessionAgent::OnChannelConnected(int32 peer_pid) {
163  DCHECK(caller_task_runner_->BelongsToCurrentThread());
164
165  VLOG(1) << "IPC: desktop <- network (" << peer_pid << ")";
166
167  desktop_pipe_.Close();
168}
169
170void DesktopSessionAgent::OnChannelError() {
171  DCHECK(caller_task_runner_->BelongsToCurrentThread());
172
173  // Make sure the channel is closed.
174  network_channel_.reset();
175  desktop_pipe_.Close();
176
177  // Notify the caller that the channel has been disconnected.
178  if (delegate_.get())
179    delegate_->OnNetworkProcessDisconnected();
180}
181
182webrtc::SharedMemory* DesktopSessionAgent::CreateSharedMemory(size_t size) {
183  DCHECK(video_capture_task_runner_->BelongsToCurrentThread());
184
185  scoped_ptr<SharedBuffer> buffer =
186      SharedBuffer::Create(this, size, next_shared_buffer_id_);
187  if (buffer) {
188    shared_buffers_++;
189
190    // |next_shared_buffer_id_| starts from 1 and incrementing it by 2 makes
191    // sure it is always odd and therefore zero is never used as a valid buffer
192    // ID.
193    //
194    // It is very unlikely (though theoretically possible) to allocate the same
195    // ID for two different buffers due to integer overflow. It should take
196    // about a year of allocating 100 new buffers every second. Practically
197    // speaking it never happens.
198    next_shared_buffer_id_ += 2;
199
200    IPC::PlatformFileForTransit handle;
201#if defined(OS_WIN)
202    handle = buffer->handle();
203#else
204    handle = base::FileDescriptor(buffer->handle(), false);
205#endif
206    SendToNetwork(new ChromotingDesktopNetworkMsg_CreateSharedBuffer(
207        buffer->id(), handle, buffer->size()));
208  }
209
210  return buffer.release();
211}
212
213DesktopSessionAgent::~DesktopSessionAgent() {
214  DCHECK(!audio_capturer_);
215  DCHECK(!desktop_environment_);
216  DCHECK(!network_channel_);
217  DCHECK(!screen_controls_);
218  DCHECK(!video_capturer_);
219}
220
221const std::string& DesktopSessionAgent::client_jid() const {
222  return client_jid_;
223}
224
225void DesktopSessionAgent::DisconnectSession() {
226  SendToNetwork(new ChromotingDesktopNetworkMsg_DisconnectSession());
227}
228
229void DesktopSessionAgent::OnLocalMouseMoved(
230    const webrtc::DesktopVector& new_pos) {
231  DCHECK(caller_task_runner_->BelongsToCurrentThread());
232
233  remote_input_filter_->LocalMouseMoved(new_pos);
234}
235
236void DesktopSessionAgent::SetDisableInputs(bool disable_inputs) {
237  DCHECK(caller_task_runner_->BelongsToCurrentThread());
238
239  // Do not expect this method to be called because it is only used by It2Me.
240  NOTREACHED();
241}
242
243void DesktopSessionAgent::ResetVideoPipeline() {
244  DCHECK(caller_task_runner_->BelongsToCurrentThread());
245
246  // This method is only used by HostExtensionSessions in the network process.
247  NOTREACHED();
248}
249
250void DesktopSessionAgent::OnStartSessionAgent(
251    const std::string& authenticated_jid,
252    const ScreenResolution& resolution,
253    bool virtual_terminal) {
254  DCHECK(caller_task_runner_->BelongsToCurrentThread());
255  DCHECK(!started_);
256  DCHECK(!audio_capturer_);
257  DCHECK(!desktop_environment_);
258  DCHECK(!input_injector_);
259  DCHECK(!screen_controls_);
260  DCHECK(!video_capturer_);
261
262  started_ = true;
263  client_jid_ = authenticated_jid;
264
265  // Enable the curtain mode.
266  delegate_->desktop_environment_factory().SetEnableCurtaining(
267      virtual_terminal);
268
269  // Create a desktop environment for the new session.
270  desktop_environment_ = delegate_->desktop_environment_factory().Create(
271      weak_factory_.GetWeakPtr());
272
273  // Create the session controller and set the initial screen resolution.
274  screen_controls_ = desktop_environment_->CreateScreenControls();
275  SetScreenResolution(resolution);
276
277  // Create the input injector.
278  input_injector_ = desktop_environment_->CreateInputInjector();
279
280  // Hook up the input filter.
281  input_tracker_.reset(new protocol::InputEventTracker(input_injector_.get()));
282  remote_input_filter_.reset(new RemoteInputFilter(input_tracker_.get()));
283
284#if defined(OS_WIN)
285  // LocalInputMonitorWin filters out an echo of the injected input before it
286  // reaches |remote_input_filter_|.
287  remote_input_filter_->SetExpectLocalEcho(false);
288#endif  // defined(OS_WIN)
289
290  // Start the input injector.
291  scoped_ptr<protocol::ClipboardStub> clipboard_stub(
292      new DesktopSesssionClipboardStub(this));
293  input_injector_->Start(clipboard_stub.Pass());
294
295  // Start the audio capturer.
296  if (delegate_->desktop_environment_factory().SupportsAudioCapture()) {
297    audio_capturer_ = desktop_environment_->CreateAudioCapturer();
298    audio_capture_task_runner_->PostTask(
299        FROM_HERE, base::Bind(&DesktopSessionAgent::StartAudioCapturer, this));
300  }
301
302  // Start the video capturer and mouse cursor monitor.
303  video_capturer_ = desktop_environment_->CreateVideoCapturer();
304  mouse_cursor_monitor_ = desktop_environment_->CreateMouseCursorMonitor();
305  video_capture_task_runner_->PostTask(
306      FROM_HERE, base::Bind(
307          &DesktopSessionAgent::StartVideoCapturerAndMouseMonitor, this));
308}
309
310void DesktopSessionAgent::OnCaptureCompleted(webrtc::DesktopFrame* frame) {
311  DCHECK(video_capture_task_runner_->BelongsToCurrentThread());
312
313  last_frame_.reset(frame);
314
315  current_size_ = frame->size();
316
317  // Serialize webrtc::DesktopFrame.
318  SerializedDesktopFrame serialized_frame;
319  serialized_frame.shared_buffer_id = frame->shared_memory()->id();
320  serialized_frame.bytes_per_row = frame->stride();
321  serialized_frame.dimensions = frame->size();
322  serialized_frame.capture_time_ms = frame->capture_time_ms();
323  serialized_frame.dpi = frame->dpi();
324  for (webrtc::DesktopRegion::Iterator i(frame->updated_region());
325       !i.IsAtEnd(); i.Advance()) {
326    serialized_frame.dirty_region.push_back(i.rect());
327  }
328
329  SendToNetwork(
330      new ChromotingDesktopNetworkMsg_CaptureCompleted(serialized_frame));
331}
332
333void DesktopSessionAgent::OnMouseCursor(webrtc::MouseCursor* cursor) {
334  DCHECK(video_capture_task_runner_->BelongsToCurrentThread());
335
336  scoped_ptr<webrtc::MouseCursor> owned_cursor(cursor);
337
338  SendToNetwork(
339      new ChromotingDesktopNetworkMsg_MouseCursor(*owned_cursor));
340}
341
342void DesktopSessionAgent::OnMouseCursorPosition(
343    webrtc::MouseCursorMonitor::CursorState state,
344    const webrtc::DesktopVector& position) {
345  // We're not subscribing to mouse position changes.
346  NOTREACHED();
347}
348
349void DesktopSessionAgent::InjectClipboardEvent(
350    const protocol::ClipboardEvent& event) {
351  DCHECK(caller_task_runner_->BelongsToCurrentThread());
352
353  std::string serialized_event;
354  if (!event.SerializeToString(&serialized_event)) {
355    LOG(ERROR) << "Failed to serialize protocol::ClipboardEvent.";
356    return;
357  }
358
359  SendToNetwork(
360      new ChromotingDesktopNetworkMsg_InjectClipboardEvent(serialized_event));
361}
362
363void DesktopSessionAgent::ProcessAudioPacket(scoped_ptr<AudioPacket> packet) {
364  DCHECK(audio_capture_task_runner_->BelongsToCurrentThread());
365
366  std::string serialized_packet;
367  if (!packet->SerializeToString(&serialized_packet)) {
368    LOG(ERROR) << "Failed to serialize AudioPacket.";
369    return;
370  }
371
372  SendToNetwork(new ChromotingDesktopNetworkMsg_AudioPacket(serialized_packet));
373}
374
375bool DesktopSessionAgent::Start(const base::WeakPtr<Delegate>& delegate,
376                                IPC::PlatformFileForTransit* desktop_pipe_out) {
377  DCHECK(caller_task_runner_->BelongsToCurrentThread());
378  DCHECK(delegate_.get() == NULL);
379
380  delegate_ = delegate;
381
382  // Create an IPC channel to communicate with the network process.
383  bool result = CreateConnectedIpcChannel(io_task_runner_,
384                                          this,
385                                          &desktop_pipe_,
386                                          &network_channel_);
387  base::PlatformFile raw_desktop_pipe = desktop_pipe_.GetPlatformFile();
388#if defined(OS_WIN)
389  *desktop_pipe_out = IPC::PlatformFileForTransit(raw_desktop_pipe);
390#elif defined(OS_POSIX)
391  *desktop_pipe_out = IPC::PlatformFileForTransit(raw_desktop_pipe, false);
392#else
393#error Unsupported platform.
394#endif
395  return result;
396}
397
398void DesktopSessionAgent::Stop() {
399  DCHECK(caller_task_runner_->BelongsToCurrentThread());
400
401  delegate_.reset();
402
403  // Make sure the channel is closed.
404  network_channel_.reset();
405
406  if (started_) {
407    started_ = false;
408
409    // Ignore any further callbacks.
410    weak_factory_.InvalidateWeakPtrs();
411    client_jid_.clear();
412
413    remote_input_filter_.reset();
414
415    // Ensure that any pressed keys or buttons are released.
416    input_tracker_->ReleaseAll();
417    input_tracker_.reset();
418
419    desktop_environment_.reset();
420    input_injector_.reset();
421    screen_controls_.reset();
422
423    // Stop the audio capturer.
424    audio_capture_task_runner_->PostTask(
425        FROM_HERE, base::Bind(&DesktopSessionAgent::StopAudioCapturer, this));
426
427    // Stop the video capturer.
428    video_capture_task_runner_->PostTask(
429        FROM_HERE, base::Bind(
430            &DesktopSessionAgent::StopVideoCapturerAndMouseMonitor, this));
431  }
432}
433
434void DesktopSessionAgent::OnCaptureFrame() {
435  if (!video_capture_task_runner_->BelongsToCurrentThread()) {
436    video_capture_task_runner_->PostTask(
437        FROM_HERE,
438        base::Bind(&DesktopSessionAgent::OnCaptureFrame, this));
439    return;
440  }
441
442  mouse_cursor_monitor_->Capture();
443
444  // webrtc::DesktopCapturer supports a very few (currently 2) outstanding
445  // capture requests. The requests are serialized on
446  // |video_capture_task_runner()| task runner. If the client issues more
447  // requests, pixel data in captured frames will likely be corrupted but
448  // stability of webrtc::DesktopCapturer will not be affected.
449  video_capturer_->Capture(webrtc::DesktopRegion());
450}
451
452void DesktopSessionAgent::OnInjectClipboardEvent(
453    const std::string& serialized_event) {
454  DCHECK(caller_task_runner_->BelongsToCurrentThread());
455
456  protocol::ClipboardEvent event;
457  if (!event.ParseFromString(serialized_event)) {
458    LOG(ERROR) << "Failed to parse protocol::ClipboardEvent.";
459    return;
460  }
461
462  // InputStub implementations must verify events themselves, so we don't need
463  // verification here. This matches HostEventDispatcher.
464  input_injector_->InjectClipboardEvent(event);
465}
466
467void DesktopSessionAgent::OnInjectKeyEvent(
468    const std::string& serialized_event) {
469  DCHECK(caller_task_runner_->BelongsToCurrentThread());
470
471  protocol::KeyEvent event;
472  if (!event.ParseFromString(serialized_event)) {
473    LOG(ERROR) << "Failed to parse protocol::KeyEvent.";
474    return;
475  }
476
477  // InputStub implementations must verify events themselves, so we need only
478  // basic verification here. This matches HostEventDispatcher.
479  if (!event.has_usb_keycode() || !event.has_pressed()) {
480    LOG(ERROR) << "Received invalid key event.";
481    return;
482  }
483
484  remote_input_filter_->InjectKeyEvent(event);
485}
486
487void DesktopSessionAgent::OnInjectTextEvent(
488    const std::string& serialized_event) {
489  DCHECK(caller_task_runner_->BelongsToCurrentThread());
490
491  protocol::TextEvent event;
492  if (!event.ParseFromString(serialized_event)) {
493    LOG(ERROR) << "Failed to parse protocol::TextEvent.";
494    return;
495  }
496
497  // InputStub implementations must verify events themselves, so we need only
498  // basic verification here. This matches HostEventDispatcher.
499  if (!event.has_text()) {
500    LOG(ERROR) << "Received invalid TextEvent.";
501    return;
502  }
503
504  remote_input_filter_->InjectTextEvent(event);
505}
506
507void DesktopSessionAgent::OnInjectMouseEvent(
508    const std::string& serialized_event) {
509  DCHECK(caller_task_runner_->BelongsToCurrentThread());
510
511  protocol::MouseEvent event;
512  if (!event.ParseFromString(serialized_event)) {
513    LOG(ERROR) << "Failed to parse protocol::MouseEvent.";
514    return;
515  }
516
517  // InputStub implementations must verify events themselves, so we don't need
518  // verification here. This matches HostEventDispatcher.
519  remote_input_filter_->InjectMouseEvent(event);
520}
521
522void DesktopSessionAgent::SetScreenResolution(
523    const ScreenResolution& resolution) {
524  DCHECK(caller_task_runner_->BelongsToCurrentThread());
525
526  if (screen_controls_ && resolution.IsEmpty())
527    screen_controls_->SetScreenResolution(resolution);
528}
529
530void DesktopSessionAgent::SendToNetwork(IPC::Message* message) {
531  if (!caller_task_runner_->BelongsToCurrentThread()) {
532    caller_task_runner_->PostTask(
533        FROM_HERE,
534        base::Bind(&DesktopSessionAgent::SendToNetwork, this, message));
535    return;
536  }
537
538  if (network_channel_) {
539    network_channel_->Send(message);
540  } else {
541    delete message;
542  }
543}
544
545void DesktopSessionAgent::StartAudioCapturer() {
546  DCHECK(audio_capture_task_runner_->BelongsToCurrentThread());
547
548  if (audio_capturer_) {
549    audio_capturer_->Start(base::Bind(&DesktopSessionAgent::ProcessAudioPacket,
550                                      this));
551  }
552}
553
554void DesktopSessionAgent::StopAudioCapturer() {
555  DCHECK(audio_capture_task_runner_->BelongsToCurrentThread());
556
557  audio_capturer_.reset();
558}
559
560void DesktopSessionAgent::StartVideoCapturerAndMouseMonitor() {
561  DCHECK(video_capture_task_runner_->BelongsToCurrentThread());
562
563  if (video_capturer_) {
564    video_capturer_->Start(this);
565  }
566
567  if (mouse_cursor_monitor_) {
568    mouse_cursor_monitor_->Init(this, webrtc::MouseCursorMonitor::SHAPE_ONLY);
569  }
570}
571
572void DesktopSessionAgent::StopVideoCapturerAndMouseMonitor() {
573  DCHECK(video_capture_task_runner_->BelongsToCurrentThread());
574
575  video_capturer_.reset();
576  last_frame_.reset();
577  mouse_cursor_monitor_.reset();
578
579  // Video capturer must delete all buffers.
580  DCHECK_EQ(shared_buffers_, 0);
581}
582
583void DesktopSessionAgent::OnSharedBufferDeleted(int id) {
584  DCHECK(video_capture_task_runner_->BelongsToCurrentThread());
585  DCHECK(id != 0);
586
587  shared_buffers_--;
588  DCHECK_GE(shared_buffers_, 0);
589  SendToNetwork(new ChromotingDesktopNetworkMsg_ReleaseSharedBuffer(id));
590}
591
592}  // namespace remoting
593