Fix mouse warp with 2x displays
[chromium-blink-merge.git] / remoting / host / desktop_session_proxy.cc
blob68d9f964011a836748bee75954bbc43e83cb180f
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_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 "remoting/protocol/capability_names.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"
33 #if defined(OS_WIN)
34 #include "base/win/scoped_handle.h"
35 #endif // defined(OS_WIN)
37 const bool kReadOnly = true;
39 namespace remoting {
41 class 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="
61 << base::SharedMemory::GetFdFromSharedMemoryHandle(handle)
62 #endif
63 << ", size=" << size;
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 base::SharedMemory::GetFdFromSharedMemoryHandle(
75 shared_memory_.handle());
76 #endif
79 private:
80 virtual ~IpcSharedBufferCore() {}
81 friend class base::RefCountedThreadSafe<IpcSharedBufferCore>;
83 int id_;
84 base::SharedMemory shared_memory_;
85 size_t size_;
87 DISALLOW_COPY_AND_ASSIGN(IpcSharedBufferCore);
90 class DesktopSessionProxy::IpcSharedBuffer : public webrtc::SharedMemory {
91 public:
92 IpcSharedBuffer(scoped_refptr<IpcSharedBufferCore> core)
93 : SharedMemory(core->memory(), core->size(),
94 core->handle(), core->id()),
95 core_(core) {
98 private:
99 scoped_refptr<IpcSharedBufferCore> core_;
101 DISALLOW_COPY_AND_ASSIGN(IpcSharedBuffer);
104 DesktopSessionProxy::DesktopSessionProxy(
105 scoped_refptr<base::SingleThreadTaskRunner> audio_capture_task_runner,
106 scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner,
107 scoped_refptr<base::SingleThreadTaskRunner> io_task_runner,
108 scoped_refptr<base::SingleThreadTaskRunner> video_capture_task_runner,
109 base::WeakPtr<ClientSessionControl> client_session_control,
110 base::WeakPtr<DesktopSessionConnector> desktop_session_connector,
111 bool virtual_terminal,
112 bool supports_touch_events)
113 : audio_capture_task_runner_(audio_capture_task_runner),
114 caller_task_runner_(caller_task_runner),
115 io_task_runner_(io_task_runner),
116 video_capture_task_runner_(video_capture_task_runner),
117 client_session_control_(client_session_control),
118 desktop_session_connector_(desktop_session_connector),
119 pending_capture_frame_requests_(0),
120 is_desktop_session_connected_(false),
121 virtual_terminal_(virtual_terminal),
122 supports_touch_events_(supports_touch_events) {
123 DCHECK(caller_task_runner_->BelongsToCurrentThread());
126 scoped_ptr<AudioCapturer> DesktopSessionProxy::CreateAudioCapturer() {
127 DCHECK(caller_task_runner_->BelongsToCurrentThread());
129 return make_scoped_ptr(new IpcAudioCapturer(this));
132 scoped_ptr<InputInjector> DesktopSessionProxy::CreateInputInjector() {
133 DCHECK(caller_task_runner_->BelongsToCurrentThread());
135 return make_scoped_ptr(new IpcInputInjector(this));
138 scoped_ptr<ScreenControls> DesktopSessionProxy::CreateScreenControls() {
139 DCHECK(caller_task_runner_->BelongsToCurrentThread());
141 return make_scoped_ptr(new IpcScreenControls(this));
144 scoped_ptr<webrtc::DesktopCapturer> DesktopSessionProxy::CreateVideoCapturer() {
145 DCHECK(caller_task_runner_->BelongsToCurrentThread());
147 return make_scoped_ptr(new IpcVideoFrameCapturer(this));
150 scoped_ptr<webrtc::MouseCursorMonitor>
151 DesktopSessionProxy::CreateMouseCursorMonitor() {
152 return make_scoped_ptr(new IpcMouseCursorMonitor(this));
155 std::string DesktopSessionProxy::GetCapabilities() const {
156 std::string result = protocol::kRateLimitResizeRequests;
157 // Ask the client to send its resolution unconditionally.
158 if (virtual_terminal_)
159 result = result + " " + protocol::kSendInitialResolution;
161 if (supports_touch_events_)
162 result = result + " " + protocol::kTouchEventsCapability;
164 return result;
167 void DesktopSessionProxy::SetCapabilities(const std::string& capabilities) {
168 // Delay creation of the desktop session until the client screen resolution is
169 // received if the desktop session requires the initial screen resolution
170 // (when |virtual_terminal_| is true) and the client is expected to
171 // sent its screen resolution (the 'sendInitialResolution' capability is
172 // supported).
173 if (virtual_terminal_ &&
174 HasCapability(capabilities, protocol::kSendInitialResolution)) {
175 VLOG(1) << "Waiting for the client screen resolution.";
176 return;
179 // Connect to the desktop session.
180 if (!is_desktop_session_connected_) {
181 is_desktop_session_connected_ = true;
182 if (desktop_session_connector_.get()) {
183 desktop_session_connector_->ConnectTerminal(
184 this, screen_resolution_, virtual_terminal_);
189 bool DesktopSessionProxy::OnMessageReceived(const IPC::Message& message) {
190 DCHECK(caller_task_runner_->BelongsToCurrentThread());
192 bool handled = true;
193 IPC_BEGIN_MESSAGE_MAP(DesktopSessionProxy, message)
194 IPC_MESSAGE_HANDLER(ChromotingDesktopNetworkMsg_AudioPacket,
195 OnAudioPacket)
196 IPC_MESSAGE_HANDLER(ChromotingDesktopNetworkMsg_CaptureCompleted,
197 OnCaptureCompleted)
198 IPC_MESSAGE_HANDLER(ChromotingDesktopNetworkMsg_MouseCursor,
199 OnMouseCursor)
200 IPC_MESSAGE_HANDLER(ChromotingDesktopNetworkMsg_CreateSharedBuffer,
201 OnCreateSharedBuffer)
202 IPC_MESSAGE_HANDLER(ChromotingDesktopNetworkMsg_ReleaseSharedBuffer,
203 OnReleaseSharedBuffer)
204 IPC_MESSAGE_HANDLER(ChromotingDesktopNetworkMsg_InjectClipboardEvent,
205 OnInjectClipboardEvent)
206 IPC_MESSAGE_HANDLER(ChromotingDesktopNetworkMsg_DisconnectSession,
207 DisconnectSession);
208 IPC_END_MESSAGE_MAP()
210 CHECK(handled) << "Received unexpected IPC type: " << message.type();
211 return handled;
214 void DesktopSessionProxy::OnChannelConnected(int32 peer_pid) {
215 DCHECK(caller_task_runner_->BelongsToCurrentThread());
217 VLOG(1) << "IPC: network <- desktop (" << peer_pid << ")";
220 void DesktopSessionProxy::OnChannelError() {
221 DCHECK(caller_task_runner_->BelongsToCurrentThread());
223 DetachFromDesktop();
226 bool DesktopSessionProxy::AttachToDesktop(
227 base::Process desktop_process,
228 IPC::PlatformFileForTransit desktop_pipe) {
229 DCHECK(caller_task_runner_->BelongsToCurrentThread());
230 DCHECK(!desktop_channel_);
231 DCHECK(!desktop_process_.IsValid());
233 // Ignore the attach notification if the client session has been disconnected
234 // already.
235 if (!client_session_control_.get())
236 return false;
238 desktop_process_ = desktop_process.Pass();
240 #if defined(OS_WIN)
241 // On Windows: |desktop_process| is a valid handle, but |desktop_pipe| needs
242 // to be duplicated from the desktop process.
243 HANDLE temp_handle;
244 if (!DuplicateHandle(desktop_process_.Handle(), desktop_pipe,
245 GetCurrentProcess(), &temp_handle, 0,
246 FALSE, DUPLICATE_SAME_ACCESS)) {
247 PLOG(ERROR) << "Failed to duplicate the desktop-to-network pipe handle";
249 desktop_process_.Close();
250 return false;
252 base::win::ScopedHandle pipe(temp_handle);
254 IPC::ChannelHandle desktop_channel_handle(pipe.Get());
256 #elif defined(OS_POSIX)
257 // On posix: |desktop_pipe| is a valid file descriptor.
258 DCHECK(desktop_pipe.auto_close);
260 IPC::ChannelHandle desktop_channel_handle(std::string(), desktop_pipe);
262 #else
263 #error Unsupported platform.
264 #endif
266 // Connect to the desktop process.
267 desktop_channel_ = IPC::ChannelProxy::Create(desktop_channel_handle,
268 IPC::Channel::MODE_CLIENT,
269 this,
270 io_task_runner_.get());
272 // Pass ID of the client (which is authenticated at this point) to the desktop
273 // session agent and start the agent.
274 SendToDesktop(new ChromotingNetworkDesktopMsg_StartSessionAgent(
275 client_session_control_->client_jid(),
276 screen_resolution_,
277 virtual_terminal_));
279 return true;
282 void DesktopSessionProxy::DetachFromDesktop() {
283 DCHECK(caller_task_runner_->BelongsToCurrentThread());
285 desktop_channel_.reset();
287 if (desktop_process_.IsValid())
288 desktop_process_.Close();
290 shared_buffers_.clear();
292 // Generate fake responses to keep the video capturer in sync.
293 while (pending_capture_frame_requests_) {
294 --pending_capture_frame_requests_;
295 PostCaptureCompleted(nullptr);
299 void DesktopSessionProxy::SetAudioCapturer(
300 const base::WeakPtr<IpcAudioCapturer>& audio_capturer) {
301 DCHECK(audio_capture_task_runner_->BelongsToCurrentThread());
303 audio_capturer_ = audio_capturer;
306 void DesktopSessionProxy::CaptureFrame() {
307 if (!caller_task_runner_->BelongsToCurrentThread()) {
308 caller_task_runner_->PostTask(
309 FROM_HERE, base::Bind(&DesktopSessionProxy::CaptureFrame, this));
310 return;
313 if (desktop_channel_) {
314 ++pending_capture_frame_requests_;
315 SendToDesktop(new ChromotingNetworkDesktopMsg_CaptureFrame());
316 } else {
317 PostCaptureCompleted(nullptr);
321 void DesktopSessionProxy::SetVideoCapturer(
322 const base::WeakPtr<IpcVideoFrameCapturer> video_capturer) {
323 DCHECK(video_capture_task_runner_->BelongsToCurrentThread());
325 video_capturer_ = video_capturer;
328 void DesktopSessionProxy::SetMouseCursorMonitor(
329 const base::WeakPtr<IpcMouseCursorMonitor>& mouse_cursor_monitor) {
330 DCHECK(video_capture_task_runner_->BelongsToCurrentThread());
332 mouse_cursor_monitor_ = mouse_cursor_monitor;
335 void DesktopSessionProxy::DisconnectSession() {
336 DCHECK(caller_task_runner_->BelongsToCurrentThread());
338 // Disconnect the client session if it hasn't been disconnected yet.
339 if (client_session_control_.get())
340 client_session_control_->DisconnectSession();
343 void DesktopSessionProxy::InjectClipboardEvent(
344 const protocol::ClipboardEvent& event) {
345 DCHECK(caller_task_runner_->BelongsToCurrentThread());
347 std::string serialized_event;
348 if (!event.SerializeToString(&serialized_event)) {
349 LOG(ERROR) << "Failed to serialize protocol::ClipboardEvent.";
350 return;
353 SendToDesktop(
354 new ChromotingNetworkDesktopMsg_InjectClipboardEvent(serialized_event));
357 void DesktopSessionProxy::InjectKeyEvent(const protocol::KeyEvent& event) {
358 DCHECK(caller_task_runner_->BelongsToCurrentThread());
360 std::string serialized_event;
361 if (!event.SerializeToString(&serialized_event)) {
362 LOG(ERROR) << "Failed to serialize protocol::KeyEvent.";
363 return;
366 SendToDesktop(
367 new ChromotingNetworkDesktopMsg_InjectKeyEvent(serialized_event));
370 void DesktopSessionProxy::InjectTextEvent(const protocol::TextEvent& event) {
371 DCHECK(caller_task_runner_->BelongsToCurrentThread());
373 std::string serialized_event;
374 if (!event.SerializeToString(&serialized_event)) {
375 LOG(ERROR) << "Failed to serialize protocol::TextEvent.";
376 return;
379 SendToDesktop(
380 new ChromotingNetworkDesktopMsg_InjectTextEvent(serialized_event));
383 void DesktopSessionProxy::InjectMouseEvent(const protocol::MouseEvent& event) {
384 DCHECK(caller_task_runner_->BelongsToCurrentThread());
386 std::string serialized_event;
387 if (!event.SerializeToString(&serialized_event)) {
388 LOG(ERROR) << "Failed to serialize protocol::MouseEvent.";
389 return;
392 SendToDesktop(
393 new ChromotingNetworkDesktopMsg_InjectMouseEvent(serialized_event));
396 void DesktopSessionProxy::InjectTouchEvent(const protocol::TouchEvent& event) {
397 DCHECK(caller_task_runner_->BelongsToCurrentThread());
399 std::string serialized_event;
400 if (!event.SerializeToString(&serialized_event)) {
401 LOG(ERROR) << "Failed to serialize protocol::TouchEvent.";
402 return;
405 SendToDesktop(
406 new ChromotingNetworkDesktopMsg_InjectTouchEvent(serialized_event));
409 void DesktopSessionProxy::StartInputInjector(
410 scoped_ptr<protocol::ClipboardStub> client_clipboard) {
411 DCHECK(caller_task_runner_->BelongsToCurrentThread());
413 client_clipboard_ = client_clipboard.Pass();
416 void DesktopSessionProxy::SetScreenResolution(
417 const ScreenResolution& resolution) {
418 DCHECK(caller_task_runner_->BelongsToCurrentThread());
420 if (resolution.IsEmpty())
421 return;
423 screen_resolution_ = resolution;
425 // Connect to the desktop session if it is not done yet.
426 if (!is_desktop_session_connected_) {
427 is_desktop_session_connected_ = true;
428 if (desktop_session_connector_.get()) {
429 desktop_session_connector_->ConnectTerminal(
430 this, screen_resolution_, virtual_terminal_);
432 return;
435 // Pass the client's resolution to both daemon and desktop session agent.
436 // Depending on the session kind the screen resolution can be set by either
437 // the daemon (for example RDP sessions on Windows) or by the desktop session
438 // agent (when sharing the physical console).
439 if (desktop_session_connector_.get())
440 desktop_session_connector_->SetScreenResolution(this, screen_resolution_);
441 SendToDesktop(
442 new ChromotingNetworkDesktopMsg_SetScreenResolution(screen_resolution_));
445 DesktopSessionProxy::~DesktopSessionProxy() {
446 DCHECK(caller_task_runner_->BelongsToCurrentThread());
448 if (desktop_session_connector_.get() && is_desktop_session_connected_)
449 desktop_session_connector_->DisconnectTerminal(this);
452 scoped_refptr<DesktopSessionProxy::IpcSharedBufferCore>
453 DesktopSessionProxy::GetSharedBufferCore(int id) {
454 DCHECK(caller_task_runner_->BelongsToCurrentThread());
456 SharedBuffers::const_iterator i = shared_buffers_.find(id);
457 if (i != shared_buffers_.end()) {
458 return i->second;
459 } else {
460 LOG(ERROR) << "Failed to find the shared buffer " << id;
461 return nullptr;
465 void DesktopSessionProxy::OnAudioPacket(const std::string& serialized_packet) {
466 DCHECK(caller_task_runner_->BelongsToCurrentThread());
468 // Parse a serialized audio packet. No further validation is done since
469 // the message was sent by more privileged process.
470 scoped_ptr<AudioPacket> packet(new AudioPacket());
471 if (!packet->ParseFromString(serialized_packet)) {
472 LOG(ERROR) << "Failed to parse AudioPacket.";
473 return;
476 // Pass a captured audio packet to |audio_capturer_|.
477 audio_capture_task_runner_->PostTask(
478 FROM_HERE, base::Bind(&IpcAudioCapturer::OnAudioPacket, audio_capturer_,
479 base::Passed(&packet)));
482 void DesktopSessionProxy::OnCreateSharedBuffer(
483 int id,
484 IPC::PlatformFileForTransit handle,
485 uint32 size) {
486 DCHECK(caller_task_runner_->BelongsToCurrentThread());
488 base::SharedMemoryHandle shm_handle = base::SharedMemoryHandle(handle);
489 scoped_refptr<IpcSharedBufferCore> shared_buffer =
490 new IpcSharedBufferCore(id, shm_handle, desktop_process_.Handle(), size);
492 if (shared_buffer->memory() != nullptr &&
493 !shared_buffers_.insert(std::make_pair(id, shared_buffer)).second) {
494 LOG(ERROR) << "Duplicate shared buffer id " << id << " encountered";
498 void DesktopSessionProxy::OnReleaseSharedBuffer(int id) {
499 DCHECK(caller_task_runner_->BelongsToCurrentThread());
501 // Drop the cached reference to the buffer.
502 shared_buffers_.erase(id);
505 void DesktopSessionProxy::OnCaptureCompleted(
506 const SerializedDesktopFrame& serialized_frame) {
507 DCHECK(caller_task_runner_->BelongsToCurrentThread());
509 // Assume that |serialized_frame| is well-formed because it was received from
510 // a more privileged process.
511 scoped_refptr<IpcSharedBufferCore> shared_buffer_core =
512 GetSharedBufferCore(serialized_frame.shared_buffer_id);
513 CHECK(shared_buffer_core.get());
515 scoped_ptr<webrtc::DesktopFrame> frame(
516 new webrtc::SharedMemoryDesktopFrame(
517 serialized_frame.dimensions, serialized_frame.bytes_per_row,
518 new IpcSharedBuffer(shared_buffer_core)));
519 frame->set_capture_time_ms(serialized_frame.capture_time_ms);
520 frame->set_dpi(serialized_frame.dpi);
522 for (size_t i = 0; i < serialized_frame.dirty_region.size(); ++i) {
523 frame->mutable_updated_region()->AddRect(serialized_frame.dirty_region[i]);
526 --pending_capture_frame_requests_;
527 PostCaptureCompleted(frame.Pass());
530 void DesktopSessionProxy::OnMouseCursor(
531 const webrtc::MouseCursor& mouse_cursor) {
532 DCHECK(caller_task_runner_->BelongsToCurrentThread());
533 PostMouseCursor(make_scoped_ptr(webrtc::MouseCursor::CopyOf(mouse_cursor)));
536 void DesktopSessionProxy::OnInjectClipboardEvent(
537 const std::string& serialized_event) {
538 DCHECK(caller_task_runner_->BelongsToCurrentThread());
540 if (client_clipboard_) {
541 protocol::ClipboardEvent event;
542 if (!event.ParseFromString(serialized_event)) {
543 LOG(ERROR) << "Failed to parse protocol::ClipboardEvent.";
544 return;
547 client_clipboard_->InjectClipboardEvent(event);
551 void DesktopSessionProxy::PostCaptureCompleted(
552 scoped_ptr<webrtc::DesktopFrame> frame) {
553 DCHECK(caller_task_runner_->BelongsToCurrentThread());
555 video_capture_task_runner_->PostTask(
556 FROM_HERE,
557 base::Bind(&IpcVideoFrameCapturer::OnCaptureCompleted, video_capturer_,
558 base::Passed(&frame)));
561 void DesktopSessionProxy::PostMouseCursor(
562 scoped_ptr<webrtc::MouseCursor> mouse_cursor) {
563 DCHECK(caller_task_runner_->BelongsToCurrentThread());
565 video_capture_task_runner_->PostTask(
566 FROM_HERE,
567 base::Bind(&IpcMouseCursorMonitor::OnMouseCursor, mouse_cursor_monitor_,
568 base::Passed(&mouse_cursor)));
571 void DesktopSessionProxy::SendToDesktop(IPC::Message* message) {
572 DCHECK(caller_task_runner_->BelongsToCurrentThread());
574 if (desktop_channel_) {
575 desktop_channel_->Send(message);
576 } else {
577 delete message;
581 // static
582 void DesktopSessionProxyTraits::Destruct(
583 const DesktopSessionProxy* desktop_session_proxy) {
584 desktop_session_proxy->caller_task_runner_->DeleteSoon(FROM_HERE,
585 desktop_session_proxy);
588 } // namespace remoting