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