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"
34 #include "base/win/scoped_handle.h"
35 #endif // defined(OS_WIN)
37 const bool kReadOnly
= true;
41 class DesktopSessionProxy::IpcSharedBufferCore
42 : public base::RefCountedThreadSafe
<IpcSharedBufferCore
> {
44 IpcSharedBufferCore(int id
,
45 base::SharedMemoryHandle handle
,
46 base::ProcessHandle process
,
50 shared_memory_(handle
, kReadOnly
, process
),
51 #else // !defined(OS_WIN)
52 shared_memory_(handle
, kReadOnly
),
53 #endif // !defined(OS_WIN)
55 if (!shared_memory_
.Map(size
)) {
56 LOG(ERROR
) << "Failed to map a shared buffer: id=" << id
58 << ", handle=" << handle
61 << base::SharedMemory::GetFdFromSharedMemoryHandle(handle
)
67 int id() { return id_
; }
68 size_t size() { return size_
; }
69 void* memory() { return shared_memory_
.memory(); }
70 webrtc::SharedMemory::Handle
handle() {
72 return shared_memory_
.handle();
74 return base::SharedMemory::GetFdFromSharedMemoryHandle(
75 shared_memory_
.handle());
80 virtual ~IpcSharedBufferCore() {}
81 friend class base::RefCountedThreadSafe
<IpcSharedBufferCore
>;
84 base::SharedMemory shared_memory_
;
87 DISALLOW_COPY_AND_ASSIGN(IpcSharedBufferCore
);
90 class DesktopSessionProxy::IpcSharedBuffer
: public webrtc::SharedMemory
{
92 IpcSharedBuffer(scoped_refptr
<IpcSharedBufferCore
> core
)
93 : SharedMemory(core
->memory(), core
->size(),
94 core
->handle(), core
->id()),
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
;
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
173 if (virtual_terminal_
&&
174 HasCapability(capabilities
, protocol::kSendInitialResolution
)) {
175 VLOG(1) << "Waiting for the client screen resolution.";
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());
193 IPC_BEGIN_MESSAGE_MAP(DesktopSessionProxy
, message
)
194 IPC_MESSAGE_HANDLER(ChromotingDesktopNetworkMsg_AudioPacket
,
196 IPC_MESSAGE_HANDLER(ChromotingDesktopNetworkMsg_CaptureCompleted
,
198 IPC_MESSAGE_HANDLER(ChromotingDesktopNetworkMsg_MouseCursor
,
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
,
208 IPC_END_MESSAGE_MAP()
210 CHECK(handled
) << "Received unexpected IPC type: " << message
.type();
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());
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
235 if (!client_session_control_
.get())
238 desktop_process_
= desktop_process
.Pass();
241 // On Windows: |desktop_process| is a valid handle, but |desktop_pipe| needs
242 // to be duplicated from the desktop process.
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();
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
);
263 #error Unsupported platform.
266 // Connect to the desktop process.
267 desktop_channel_
= IPC::ChannelProxy::Create(desktop_channel_handle
,
268 IPC::Channel::MODE_CLIENT
,
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(),
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));
313 if (desktop_channel_
) {
314 ++pending_capture_frame_requests_
;
315 SendToDesktop(new ChromotingNetworkDesktopMsg_CaptureFrame());
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.";
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.";
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.";
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.";
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.";
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())
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_
);
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_
);
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()) {
460 LOG(ERROR
) << "Failed to find the shared buffer " << id
;
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.";
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(
484 IPC::PlatformFileForTransit handle
,
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.";
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(
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(
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
);
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