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.
5 #include "remoting/host/desktop_session_proxy.h"
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"
31 #include "base/win/scoped_handle.h"
32 #endif // defined(OS_WIN)
34 const bool kReadOnly = true;
35 const char kSendInitialResolution[] = "sendInitialResolution";
36 const char kRateLimitResizeRequests[] = "rateLimitResizeRequests";
40 class DesktopSessionProxy::IpcSharedBufferCore
41 : public base::RefCountedThreadSafe<IpcSharedBufferCore> {
43 IpcSharedBufferCore(int id,
44 base::SharedMemoryHandle handle,
45 base::ProcessHandle process,
49 shared_memory_(handle, kReadOnly, process),
50 #else // !defined(OS_WIN)
51 shared_memory_(handle, kReadOnly),
52 #endif // !defined(OS_WIN)
54 if (!shared_memory_.Map(size)) {
55 LOG(ERROR) << "Failed to map a shared buffer: id=" << id
57 << ", handle=" << handle
59 << ", handle.fd=" << handle.fd
65 int id() { return id_; }
66 size_t size() { return size_; }
67 void* memory() { return shared_memory_.memory(); }
68 webrtc::SharedMemory::Handle handle() {
70 return shared_memory_.handle();
72 return shared_memory_.handle().fd;
77 virtual ~IpcSharedBufferCore() {}
78 friend class base::RefCountedThreadSafe<IpcSharedBufferCore>;
81 base::SharedMemory shared_memory_;
84 DISALLOW_COPY_AND_ASSIGN(IpcSharedBufferCore);
87 class DesktopSessionProxy::IpcSharedBuffer : public webrtc::SharedMemory {
89 IpcSharedBuffer(scoped_refptr<IpcSharedBufferCore> core)
90 : SharedMemory(core->memory(), core->size(),
91 core->handle(), core->id()),
96 scoped_refptr<IpcSharedBufferCore> core_;
98 DISALLOW_COPY_AND_ASSIGN(IpcSharedBuffer);
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());
122 scoped_ptr<AudioCapturer> DesktopSessionProxy::CreateAudioCapturer() {
123 DCHECK(caller_task_runner_->BelongsToCurrentThread());
125 return scoped_ptr<AudioCapturer>(new IpcAudioCapturer(this));
128 scoped_ptr<InputInjector> DesktopSessionProxy::CreateInputInjector() {
129 DCHECK(caller_task_runner_->BelongsToCurrentThread());
131 return scoped_ptr<InputInjector>(new IpcInputInjector(this));
134 scoped_ptr<ScreenControls> DesktopSessionProxy::CreateScreenControls() {
135 DCHECK(caller_task_runner_->BelongsToCurrentThread());
137 return scoped_ptr<ScreenControls>(new IpcScreenControls(this));
140 scoped_ptr<webrtc::ScreenCapturer> DesktopSessionProxy::CreateVideoCapturer() {
141 DCHECK(caller_task_runner_->BelongsToCurrentThread());
143 return scoped_ptr<webrtc::ScreenCapturer>(new IpcVideoFrameCapturer(this));
146 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;
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
160 if (virtual_terminal_ &&
161 HasCapability(capabilities, kSendInitialResolution)) {
162 VLOG(1) << "Waiting for the client screen resolution.";
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_);
176 bool DesktopSessionProxy::OnMessageReceived(const IPC::Message& message) {
177 DCHECK(caller_task_runner_->BelongsToCurrentThread());
180 IPC_BEGIN_MESSAGE_MAP(DesktopSessionProxy, message)
181 IPC_MESSAGE_HANDLER(ChromotingDesktopNetworkMsg_AudioPacket,
183 IPC_MESSAGE_HANDLER(ChromotingDesktopNetworkMsg_CaptureCompleted,
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,
195 IPC_END_MESSAGE_MAP()
197 CHECK(handled) << "Received unexpected IPC type: " << message.type();
201 void DesktopSessionProxy::OnChannelConnected(int32 peer_pid) {
202 DCHECK(caller_task_runner_->BelongsToCurrentThread());
204 VLOG(1) << "IPC: network <- desktop (" << peer_pid << ")";
207 void DesktopSessionProxy::OnChannelError() {
208 DCHECK(caller_task_runner_->BelongsToCurrentThread());
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);
220 // Ignore the attach notification if the client session has been disconnected
222 if (!client_session_control_.get()) {
223 base::CloseProcessHandle(desktop_process);
227 desktop_process_ = desktop_process;
230 // On Windows: |desktop_process| is a valid handle, but |desktop_pipe| needs
231 // to be duplicated from the desktop process.
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";
237 desktop_process_ = base::kNullProcessHandle;
238 base::CloseProcessHandle(desktop_process);
241 base::win::ScopedHandle pipe(temp_handle);
243 IPC::ChannelHandle desktop_channel_handle(pipe);
245 #elif defined(OS_POSIX)
246 // On posix: |desktop_pipe| is a valid file descriptor.
247 DCHECK(desktop_pipe.auto_close);
249 IPC::ChannelHandle desktop_channel_handle(std::string(), desktop_pipe);
252 #error Unsupported platform.
255 // Connect to the desktop process.
256 desktop_channel_ = IPC::ChannelProxy::Create(desktop_channel_handle,
257 IPC::Channel::MODE_CLIENT,
259 io_task_runner_.get());
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(),
271 void DesktopSessionProxy::DetachFromDesktop() {
272 DCHECK(caller_task_runner_->BelongsToCurrentThread());
274 desktop_channel_.reset();
276 if (desktop_process_ != base::kNullProcessHandle) {
277 base::CloseProcessHandle(desktop_process_);
278 desktop_process_ = base::kNullProcessHandle;
281 shared_buffers_.clear();
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>());
290 void DesktopSessionProxy::SetAudioCapturer(
291 const base::WeakPtr<IpcAudioCapturer>& audio_capturer) {
292 DCHECK(audio_capture_task_runner_->BelongsToCurrentThread());
294 audio_capturer_ = audio_capturer;
297 void DesktopSessionProxy::CaptureFrame() {
298 if (!caller_task_runner_->BelongsToCurrentThread()) {
299 caller_task_runner_->PostTask(
300 FROM_HERE, base::Bind(&DesktopSessionProxy::CaptureFrame, this));
304 if (desktop_channel_) {
305 ++pending_capture_frame_requests_;
306 SendToDesktop(new ChromotingNetworkDesktopMsg_CaptureFrame());
308 PostCaptureCompleted(scoped_ptr<webrtc::DesktopFrame>());
312 void DesktopSessionProxy::SetVideoCapturer(
313 const base::WeakPtr<IpcVideoFrameCapturer> video_capturer) {
314 DCHECK(video_capture_task_runner_->BelongsToCurrentThread());
316 video_capturer_ = video_capturer;
319 void DesktopSessionProxy::DisconnectSession() {
320 DCHECK(caller_task_runner_->BelongsToCurrentThread());
322 // Disconnect the client session if it hasn't been disconnected yet.
323 if (client_session_control_.get())
324 client_session_control_->DisconnectSession();
327 void DesktopSessionProxy::InjectClipboardEvent(
328 const protocol::ClipboardEvent& event) {
329 DCHECK(caller_task_runner_->BelongsToCurrentThread());
331 std::string serialized_event;
332 if (!event.SerializeToString(&serialized_event)) {
333 LOG(ERROR) << "Failed to serialize protocol::ClipboardEvent.";
338 new ChromotingNetworkDesktopMsg_InjectClipboardEvent(serialized_event));
341 void DesktopSessionProxy::InjectKeyEvent(const protocol::KeyEvent& event) {
342 DCHECK(caller_task_runner_->BelongsToCurrentThread());
344 std::string serialized_event;
345 if (!event.SerializeToString(&serialized_event)) {
346 LOG(ERROR) << "Failed to serialize protocol::KeyEvent.";
351 new ChromotingNetworkDesktopMsg_InjectKeyEvent(serialized_event));
354 void DesktopSessionProxy::InjectTextEvent(const protocol::TextEvent& event) {
355 DCHECK(caller_task_runner_->BelongsToCurrentThread());
357 std::string serialized_event;
358 if (!event.SerializeToString(&serialized_event)) {
359 LOG(ERROR) << "Failed to serialize protocol::TextEvent.";
364 new ChromotingNetworkDesktopMsg_InjectTextEvent(serialized_event));
367 void DesktopSessionProxy::InjectMouseEvent(const protocol::MouseEvent& event) {
368 DCHECK(caller_task_runner_->BelongsToCurrentThread());
370 std::string serialized_event;
371 if (!event.SerializeToString(&serialized_event)) {
372 LOG(ERROR) << "Failed to serialize protocol::MouseEvent.";
377 new ChromotingNetworkDesktopMsg_InjectMouseEvent(serialized_event));
380 void DesktopSessionProxy::StartInputInjector(
381 scoped_ptr<protocol::ClipboardStub> client_clipboard) {
382 DCHECK(caller_task_runner_->BelongsToCurrentThread());
384 client_clipboard_ = client_clipboard.Pass();
387 void DesktopSessionProxy::SetScreenResolution(
388 const ScreenResolution& resolution) {
389 DCHECK(caller_task_runner_->BelongsToCurrentThread());
391 if (resolution.IsEmpty())
394 screen_resolution_ = resolution;
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_);
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_);
413 new ChromotingNetworkDesktopMsg_SetScreenResolution(screen_resolution_));
416 DesktopSessionProxy::~DesktopSessionProxy() {
417 DCHECK(caller_task_runner_->BelongsToCurrentThread());
419 if (desktop_session_connector_.get() && is_desktop_session_connected_)
420 desktop_session_connector_->DisconnectTerminal(this);
422 if (desktop_process_ != base::kNullProcessHandle) {
423 base::CloseProcessHandle(desktop_process_);
424 desktop_process_ = base::kNullProcessHandle;
428 scoped_refptr<DesktopSessionProxy::IpcSharedBufferCore>
429 DesktopSessionProxy::GetSharedBufferCore(int id) {
430 DCHECK(caller_task_runner_->BelongsToCurrentThread());
432 SharedBuffers::const_iterator i = shared_buffers_.find(id);
433 if (i != shared_buffers_.end()) {
436 LOG(ERROR) << "Failed to find the shared buffer " << id;
441 void DesktopSessionProxy::OnAudioPacket(const std::string& serialized_packet) {
442 DCHECK(caller_task_runner_->BelongsToCurrentThread());
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.";
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)));
458 void DesktopSessionProxy::OnCreateSharedBuffer(
460 IPC::PlatformFileForTransit handle,
462 DCHECK(caller_task_runner_->BelongsToCurrentThread());
464 scoped_refptr<IpcSharedBufferCore> shared_buffer =
465 new IpcSharedBufferCore(id, handle, desktop_process_, size);
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";
473 void DesktopSessionProxy::OnReleaseSharedBuffer(int id) {
474 DCHECK(caller_task_runner_->BelongsToCurrentThread());
476 // Drop the cached reference to the buffer.
477 shared_buffers_.erase(id);
480 void DesktopSessionProxy::OnCaptureCompleted(
481 const SerializedDesktopFrame& serialized_frame) {
482 DCHECK(caller_task_runner_->BelongsToCurrentThread());
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());
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);
497 for (size_t i = 0; i < serialized_frame.dirty_region.size(); ++i) {
498 frame->mutable_updated_region()->AddRect(serialized_frame.dirty_region[i]);
501 --pending_capture_frame_requests_;
502 PostCaptureCompleted(frame.Pass());
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)));
512 void DesktopSessionProxy::OnInjectClipboardEvent(
513 const std::string& serialized_event) {
514 DCHECK(caller_task_runner_->BelongsToCurrentThread());
516 if (client_clipboard_) {
517 protocol::ClipboardEvent event;
518 if (!event.ParseFromString(serialized_event)) {
519 LOG(ERROR) << "Failed to parse protocol::ClipboardEvent.";
523 client_clipboard_->InjectClipboardEvent(event);
527 void DesktopSessionProxy::PostCaptureCompleted(
528 scoped_ptr<webrtc::DesktopFrame> frame) {
529 DCHECK(caller_task_runner_->BelongsToCurrentThread());
531 video_capture_task_runner_->PostTask(
533 base::Bind(&IpcVideoFrameCapturer::OnCaptureCompleted, video_capturer_,
534 base::Passed(&frame)));
537 void DesktopSessionProxy::PostCursorShape(
538 scoped_ptr<webrtc::MouseCursorShape> cursor_shape) {
539 DCHECK(caller_task_runner_->BelongsToCurrentThread());
541 video_capture_task_runner_->PostTask(
543 base::Bind(&IpcVideoFrameCapturer::OnCursorShapeChanged, video_capturer_,
544 base::Passed(&cursor_shape)));
547 void DesktopSessionProxy::SendToDesktop(IPC::Message* message) {
548 DCHECK(caller_task_runner_->BelongsToCurrentThread());
550 if (desktop_channel_) {
551 desktop_channel_->Send(message);
558 void DesktopSessionProxyTraits::Destruct(
559 const DesktopSessionProxy* desktop_session_proxy) {
560 desktop_session_proxy->caller_task_runner_->DeleteSoon(FROM_HERE,
561 desktop_session_proxy);
564 } // namespace remoting