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_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" 29 30 #if defined(OS_WIN) 31 #include "base/win/scoped_handle.h" 32 #endif // defined(OS_WIN) 33 34 const bool kReadOnly = true; 35 const char kSendInitialResolution[] = "sendInitialResolution"; 36 const char kRateLimitResizeRequests[] = "rateLimitResizeRequests"; 37 38 namespace remoting { 39 40 class DesktopSessionProxy::IpcSharedBufferCore 41 : public base::RefCountedThreadSafe<IpcSharedBufferCore> { 42 public: 43 IpcSharedBufferCore(int id, 44 base::SharedMemoryHandle handle, 45 base::ProcessHandle process, 46 size_t size) 47 : id_(id), 48 #if defined(OS_WIN) 49 shared_memory_(handle, kReadOnly, process), 50 #else // !defined(OS_WIN) 51 shared_memory_(handle, kReadOnly), 52 #endif // !defined(OS_WIN) 53 size_(size) { 54 if (!shared_memory_.Map(size)) { 55 LOG(ERROR) << "Failed to map a shared buffer: id=" << id 56 #if defined(OS_WIN) 57 << ", handle=" << handle 58 #else 59 << ", handle.fd=" << handle.fd 60 #endif 61 << ", size=" << size; 62 } 63 } 64 65 int id() { return id_; } 66 size_t size() { return size_; } 67 void* memory() { return shared_memory_.memory(); } 68 webrtc::SharedMemory::Handle handle() { 69 #if defined(OS_WIN) 70 return shared_memory_.handle(); 71 #else 72 return shared_memory_.handle().fd; 73 #endif 74 } 75 76 private: 77 virtual ~IpcSharedBufferCore() {} 78 friend class base::RefCountedThreadSafe<IpcSharedBufferCore>; 79 80 int id_; 81 base::SharedMemory shared_memory_; 82 size_t size_; 83 84 DISALLOW_COPY_AND_ASSIGN(IpcSharedBufferCore); 85 }; 86 87 class DesktopSessionProxy::IpcSharedBuffer : public webrtc::SharedMemory { 88 public: 89 IpcSharedBuffer(scoped_refptr<IpcSharedBufferCore> core) 90 : SharedMemory(core->memory(), core->size(), 91 core->handle(), core->id()), 92 core_(core) { 93 } 94 95 private: 96 scoped_refptr<IpcSharedBufferCore> core_; 97 98 DISALLOW_COPY_AND_ASSIGN(IpcSharedBuffer); 99 }; 100 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()); 120 } 121 122 scoped_ptr<AudioCapturer> DesktopSessionProxy::CreateAudioCapturer() { 123 DCHECK(caller_task_runner_->BelongsToCurrentThread()); 124 125 return scoped_ptr<AudioCapturer>(new IpcAudioCapturer(this)); 126 } 127 128 scoped_ptr<InputInjector> DesktopSessionProxy::CreateInputInjector() { 129 DCHECK(caller_task_runner_->BelongsToCurrentThread()); 130 131 return scoped_ptr<InputInjector>(new IpcInputInjector(this)); 132 } 133 134 scoped_ptr<ScreenControls> DesktopSessionProxy::CreateScreenControls() { 135 DCHECK(caller_task_runner_->BelongsToCurrentThread()); 136 137 return scoped_ptr<ScreenControls>(new IpcScreenControls(this)); 138 } 139 140 scoped_ptr<webrtc::ScreenCapturer> DesktopSessionProxy::CreateVideoCapturer() { 141 DCHECK(caller_task_runner_->BelongsToCurrentThread()); 142 143 return scoped_ptr<webrtc::ScreenCapturer>(new IpcVideoFrameCapturer(this)); 144 } 145 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; 151 return result; 152 } 153 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 159 // supported). 160 if (virtual_terminal_ && 161 HasCapability(capabilities, kSendInitialResolution)) { 162 VLOG(1) << "Waiting for the client screen resolution."; 163 return; 164 } 165 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_); 172 } 173 } 174 } 175 176 bool DesktopSessionProxy::OnMessageReceived(const IPC::Message& message) { 177 DCHECK(caller_task_runner_->BelongsToCurrentThread()); 178 179 bool handled = true; 180 IPC_BEGIN_MESSAGE_MAP(DesktopSessionProxy, message) 181 IPC_MESSAGE_HANDLER(ChromotingDesktopNetworkMsg_AudioPacket, 182 OnAudioPacket) 183 IPC_MESSAGE_HANDLER(ChromotingDesktopNetworkMsg_CaptureCompleted, 184 OnCaptureCompleted) 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, 194 DisconnectSession); 195 IPC_END_MESSAGE_MAP() 196 197 CHECK(handled) << "Received unexpected IPC type: " << message.type(); 198 return handled; 199 } 200 201 void DesktopSessionProxy::OnChannelConnected(int32 peer_pid) { 202 DCHECK(caller_task_runner_->BelongsToCurrentThread()); 203 204 VLOG(1) << "IPC: network <- desktop (" << peer_pid << ")"; 205 } 206 207 void DesktopSessionProxy::OnChannelError() { 208 DCHECK(caller_task_runner_->BelongsToCurrentThread()); 209 210 DetachFromDesktop(); 211 } 212 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); 219 220 // Ignore the attach notification if the client session has been disconnected 221 // already. 222 if (!client_session_control_.get()) { 223 base::CloseProcessHandle(desktop_process); 224 return false; 225 } 226 227 desktop_process_ = desktop_process; 228 229 #if defined(OS_WIN) 230 // On Windows: |desktop_process| is a valid handle, but |desktop_pipe| needs 231 // to be duplicated from the desktop process. 232 HANDLE temp_handle; 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"; 236 237 desktop_process_ = base::kNullProcessHandle; 238 base::CloseProcessHandle(desktop_process); 239 return false; 240 } 241 base::win::ScopedHandle pipe(temp_handle); 242 243 IPC::ChannelHandle desktop_channel_handle(pipe); 244 245 #elif defined(OS_POSIX) 246 // On posix: |desktop_pipe| is a valid file descriptor. 247 DCHECK(desktop_pipe.auto_close); 248 249 IPC::ChannelHandle desktop_channel_handle(std::string(), desktop_pipe); 250 251 #else 252 #error Unsupported platform. 253 #endif 254 255 // Connect to the desktop process. 256 desktop_channel_ = IPC::ChannelProxy::Create(desktop_channel_handle, 257 IPC::Channel::MODE_CLIENT, 258 this, 259 io_task_runner_.get()); 260 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(), 265 screen_resolution_, 266 virtual_terminal_)); 267 268 return true; 269 } 270 271 void DesktopSessionProxy::DetachFromDesktop() { 272 DCHECK(caller_task_runner_->BelongsToCurrentThread()); 273 274 desktop_channel_.reset(); 275 276 if (desktop_process_ != base::kNullProcessHandle) { 277 base::CloseProcessHandle(desktop_process_); 278 desktop_process_ = base::kNullProcessHandle; 279 } 280 281 shared_buffers_.clear(); 282 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>()); 287 } 288 } 289 290 void DesktopSessionProxy::SetAudioCapturer( 291 const base::WeakPtr<IpcAudioCapturer>& audio_capturer) { 292 DCHECK(audio_capture_task_runner_->BelongsToCurrentThread()); 293 294 audio_capturer_ = audio_capturer; 295 } 296 297 void DesktopSessionProxy::CaptureFrame() { 298 if (!caller_task_runner_->BelongsToCurrentThread()) { 299 caller_task_runner_->PostTask( 300 FROM_HERE, base::Bind(&DesktopSessionProxy::CaptureFrame, this)); 301 return; 302 } 303 304 if (desktop_channel_) { 305 ++pending_capture_frame_requests_; 306 SendToDesktop(new ChromotingNetworkDesktopMsg_CaptureFrame()); 307 } else { 308 PostCaptureCompleted(scoped_ptr<webrtc::DesktopFrame>()); 309 } 310 } 311 312 void DesktopSessionProxy::SetVideoCapturer( 313 const base::WeakPtr<IpcVideoFrameCapturer> video_capturer) { 314 DCHECK(video_capture_task_runner_->BelongsToCurrentThread()); 315 316 video_capturer_ = video_capturer; 317 } 318 319 void DesktopSessionProxy::DisconnectSession() { 320 DCHECK(caller_task_runner_->BelongsToCurrentThread()); 321 322 // Disconnect the client session if it hasn't been disconnected yet. 323 if (client_session_control_.get()) 324 client_session_control_->DisconnectSession(); 325 } 326 327 void DesktopSessionProxy::InjectClipboardEvent( 328 const protocol::ClipboardEvent& event) { 329 DCHECK(caller_task_runner_->BelongsToCurrentThread()); 330 331 std::string serialized_event; 332 if (!event.SerializeToString(&serialized_event)) { 333 LOG(ERROR) << "Failed to serialize protocol::ClipboardEvent."; 334 return; 335 } 336 337 SendToDesktop( 338 new ChromotingNetworkDesktopMsg_InjectClipboardEvent(serialized_event)); 339 } 340 341 void DesktopSessionProxy::InjectKeyEvent(const protocol::KeyEvent& event) { 342 DCHECK(caller_task_runner_->BelongsToCurrentThread()); 343 344 std::string serialized_event; 345 if (!event.SerializeToString(&serialized_event)) { 346 LOG(ERROR) << "Failed to serialize protocol::KeyEvent."; 347 return; 348 } 349 350 SendToDesktop( 351 new ChromotingNetworkDesktopMsg_InjectKeyEvent(serialized_event)); 352 } 353 354 void DesktopSessionProxy::InjectTextEvent(const protocol::TextEvent& event) { 355 DCHECK(caller_task_runner_->BelongsToCurrentThread()); 356 357 std::string serialized_event; 358 if (!event.SerializeToString(&serialized_event)) { 359 LOG(ERROR) << "Failed to serialize protocol::TextEvent."; 360 return; 361 } 362 363 SendToDesktop( 364 new ChromotingNetworkDesktopMsg_InjectTextEvent(serialized_event)); 365 } 366 367 void DesktopSessionProxy::InjectMouseEvent(const protocol::MouseEvent& event) { 368 DCHECK(caller_task_runner_->BelongsToCurrentThread()); 369 370 std::string serialized_event; 371 if (!event.SerializeToString(&serialized_event)) { 372 LOG(ERROR) << "Failed to serialize protocol::MouseEvent."; 373 return; 374 } 375 376 SendToDesktop( 377 new ChromotingNetworkDesktopMsg_InjectMouseEvent(serialized_event)); 378 } 379 380 void DesktopSessionProxy::StartInputInjector( 381 scoped_ptr<protocol::ClipboardStub> client_clipboard) { 382 DCHECK(caller_task_runner_->BelongsToCurrentThread()); 383 384 client_clipboard_ = client_clipboard.Pass(); 385 } 386 387 void DesktopSessionProxy::SetScreenResolution( 388 const ScreenResolution& resolution) { 389 DCHECK(caller_task_runner_->BelongsToCurrentThread()); 390 391 if (resolution.IsEmpty()) 392 return; 393 394 screen_resolution_ = resolution; 395 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_); 402 } 403 return; 404 } 405 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_); 412 SendToDesktop( 413 new ChromotingNetworkDesktopMsg_SetScreenResolution(screen_resolution_)); 414 } 415 416 DesktopSessionProxy::~DesktopSessionProxy() { 417 DCHECK(caller_task_runner_->BelongsToCurrentThread()); 418 419 if (desktop_session_connector_.get() && is_desktop_session_connected_) 420 desktop_session_connector_->DisconnectTerminal(this); 421 422 if (desktop_process_ != base::kNullProcessHandle) { 423 base::CloseProcessHandle(desktop_process_); 424 desktop_process_ = base::kNullProcessHandle; 425 } 426 } 427 428 scoped_refptr<DesktopSessionProxy::IpcSharedBufferCore> 429 DesktopSessionProxy::GetSharedBufferCore(int id) { 430 DCHECK(caller_task_runner_->BelongsToCurrentThread()); 431 432 SharedBuffers::const_iterator i = shared_buffers_.find(id); 433 if (i != shared_buffers_.end()) { 434 return i->second; 435 } else { 436 LOG(ERROR) << "Failed to find the shared buffer " << id; 437 return NULL; 438 } 439 } 440 441 void DesktopSessionProxy::OnAudioPacket(const std::string& serialized_packet) { 442 DCHECK(caller_task_runner_->BelongsToCurrentThread()); 443 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."; 449 return; 450 } 451 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))); 456 } 457 458 void DesktopSessionProxy::OnCreateSharedBuffer( 459 int id, 460 IPC::PlatformFileForTransit handle, 461 uint32 size) { 462 DCHECK(caller_task_runner_->BelongsToCurrentThread()); 463 464 scoped_refptr<IpcSharedBufferCore> shared_buffer = 465 new IpcSharedBufferCore(id, handle, desktop_process_, size); 466 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"; 470 } 471 } 472 473 void DesktopSessionProxy::OnReleaseSharedBuffer(int id) { 474 DCHECK(caller_task_runner_->BelongsToCurrentThread()); 475 476 // Drop the cached reference to the buffer. 477 shared_buffers_.erase(id); 478 } 479 480 void DesktopSessionProxy::OnCaptureCompleted( 481 const SerializedDesktopFrame& serialized_frame) { 482 DCHECK(caller_task_runner_->BelongsToCurrentThread()); 483 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()); 489 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); 496 497 for (size_t i = 0; i < serialized_frame.dirty_region.size(); ++i) { 498 frame->mutable_updated_region()->AddRect(serialized_frame.dirty_region[i]); 499 } 500 501 --pending_capture_frame_requests_; 502 PostCaptureCompleted(frame.Pass()); 503 } 504 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))); 510 } 511 512 void DesktopSessionProxy::OnInjectClipboardEvent( 513 const std::string& serialized_event) { 514 DCHECK(caller_task_runner_->BelongsToCurrentThread()); 515 516 if (client_clipboard_) { 517 protocol::ClipboardEvent event; 518 if (!event.ParseFromString(serialized_event)) { 519 LOG(ERROR) << "Failed to parse protocol::ClipboardEvent."; 520 return; 521 } 522 523 client_clipboard_->InjectClipboardEvent(event); 524 } 525 } 526 527 void DesktopSessionProxy::PostCaptureCompleted( 528 scoped_ptr<webrtc::DesktopFrame> frame) { 529 DCHECK(caller_task_runner_->BelongsToCurrentThread()); 530 531 video_capture_task_runner_->PostTask( 532 FROM_HERE, 533 base::Bind(&IpcVideoFrameCapturer::OnCaptureCompleted, video_capturer_, 534 base::Passed(&frame))); 535 } 536 537 void DesktopSessionProxy::PostCursorShape( 538 scoped_ptr<webrtc::MouseCursorShape> cursor_shape) { 539 DCHECK(caller_task_runner_->BelongsToCurrentThread()); 540 541 video_capture_task_runner_->PostTask( 542 FROM_HERE, 543 base::Bind(&IpcVideoFrameCapturer::OnCursorShapeChanged, video_capturer_, 544 base::Passed(&cursor_shape))); 545 } 546 547 void DesktopSessionProxy::SendToDesktop(IPC::Message* message) { 548 DCHECK(caller_task_runner_->BelongsToCurrentThread()); 549 550 if (desktop_channel_) { 551 desktop_channel_->Send(message); 552 } else { 553 delete message; 554 } 555 } 556 557 // static 558 void DesktopSessionProxyTraits::Destruct( 559 const DesktopSessionProxy* desktop_session_proxy) { 560 desktop_session_proxy->caller_task_runner_->DeleteSoon(FROM_HERE, 561 desktop_session_proxy); 562 } 563 564 } // namespace remoting 565