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