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 "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"
33 #include "base/win/scoped_handle.h"
34 #endif // defined(OS_WIN)
36 const bool kReadOnly
= true;
37 const char kSendInitialResolution
[] = "sendInitialResolution";
38 const char kRateLimitResizeRequests
[] = "rateLimitResizeRequests";
42 class DesktopSessionProxy::IpcSharedBufferCore
43 : public base::RefCountedThreadSafe
<IpcSharedBufferCore
> {
45 IpcSharedBufferCore(int id
,
46 base::SharedMemoryHandle handle
,
47 base::ProcessHandle process
,
51 shared_memory_(handle
, kReadOnly
, process
),
52 #else // !defined(OS_WIN)
53 shared_memory_(handle
, kReadOnly
),
54 #endif // !defined(OS_WIN)
56 if (!shared_memory_
.Map(size
)) {
57 LOG(ERROR
) << "Failed to map a shared buffer: id=" << id
59 << ", handle=" << handle
61 << ", handle.fd=" << handle
.fd
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 shared_memory_
.handle().fd
;
79 virtual ~IpcSharedBufferCore() {}
80 friend class base::RefCountedThreadSafe
<IpcSharedBufferCore
>;
83 base::SharedMemory shared_memory_
;
86 DISALLOW_COPY_AND_ASSIGN(IpcSharedBufferCore
);
89 class DesktopSessionProxy::IpcSharedBuffer
: public webrtc::SharedMemory
{
91 IpcSharedBuffer(scoped_refptr
<IpcSharedBufferCore
> core
)
92 : SharedMemory(core
->memory(), core
->size(),
93 core
->handle(), core
->id()),
98 scoped_refptr
<IpcSharedBufferCore
> core_
;
100 DISALLOW_COPY_AND_ASSIGN(IpcSharedBuffer
);
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());
124 scoped_ptr
<AudioCapturer
> DesktopSessionProxy::CreateAudioCapturer() {
125 DCHECK(caller_task_runner_
->BelongsToCurrentThread());
127 return scoped_ptr
<AudioCapturer
>(new IpcAudioCapturer(this));
130 scoped_ptr
<InputInjector
> DesktopSessionProxy::CreateInputInjector() {
131 DCHECK(caller_task_runner_
->BelongsToCurrentThread());
133 return scoped_ptr
<InputInjector
>(new IpcInputInjector(this));
136 scoped_ptr
<ScreenControls
> DesktopSessionProxy::CreateScreenControls() {
137 DCHECK(caller_task_runner_
->BelongsToCurrentThread());
139 return scoped_ptr
<ScreenControls
>(new IpcScreenControls(this));
142 scoped_ptr
<webrtc::DesktopCapturer
> DesktopSessionProxy::CreateVideoCapturer() {
143 DCHECK(caller_task_runner_
->BelongsToCurrentThread());
145 return scoped_ptr
<webrtc::DesktopCapturer
>(new IpcVideoFrameCapturer(this));
148 scoped_ptr
<webrtc::MouseCursorMonitor
>
149 DesktopSessionProxy::CreateMouseCursorMonitor() {
150 return scoped_ptr
<webrtc::MouseCursorMonitor
>(
151 new IpcMouseCursorMonitor(this));
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
;
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
168 if (virtual_terminal_
&&
169 HasCapability(capabilities
, kSendInitialResolution
)) {
170 VLOG(1) << "Waiting for the client screen resolution.";
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_
);
184 bool DesktopSessionProxy::OnMessageReceived(const IPC::Message
& message
) {
185 DCHECK(caller_task_runner_
->BelongsToCurrentThread());
188 IPC_BEGIN_MESSAGE_MAP(DesktopSessionProxy
, message
)
189 IPC_MESSAGE_HANDLER(ChromotingDesktopNetworkMsg_AudioPacket
,
191 IPC_MESSAGE_HANDLER(ChromotingDesktopNetworkMsg_CaptureCompleted
,
193 IPC_MESSAGE_HANDLER(ChromotingDesktopNetworkMsg_MouseCursor
,
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
,
203 IPC_END_MESSAGE_MAP()
205 CHECK(handled
) << "Received unexpected IPC type: " << message
.type();
209 void DesktopSessionProxy::OnChannelConnected(int32 peer_pid
) {
210 DCHECK(caller_task_runner_
->BelongsToCurrentThread());
212 VLOG(1) << "IPC: network <- desktop (" << peer_pid
<< ")";
215 void DesktopSessionProxy::OnChannelError() {
216 DCHECK(caller_task_runner_
->BelongsToCurrentThread());
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
);
228 // Ignore the attach notification if the client session has been disconnected
230 if (!client_session_control_
.get()) {
231 base::CloseProcessHandle(desktop_process
);
235 desktop_process_
= desktop_process
;
238 // On Windows: |desktop_process| is a valid handle, but |desktop_pipe| needs
239 // to be duplicated from the desktop process.
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";
245 desktop_process_
= base::kNullProcessHandle
;
246 base::CloseProcessHandle(desktop_process
);
249 base::win::ScopedHandle
pipe(temp_handle
);
251 IPC::ChannelHandle
desktop_channel_handle(pipe
);
253 #elif defined(OS_POSIX)
254 // On posix: |desktop_pipe| is a valid file descriptor.
255 DCHECK(desktop_pipe
.auto_close
);
257 IPC::ChannelHandle
desktop_channel_handle(std::string(), desktop_pipe
);
260 #error Unsupported platform.
263 // Connect to the desktop process.
264 desktop_channel_
= IPC::ChannelProxy::Create(desktop_channel_handle
,
265 IPC::Channel::MODE_CLIENT
,
267 io_task_runner_
.get());
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(),
279 void DesktopSessionProxy::DetachFromDesktop() {
280 DCHECK(caller_task_runner_
->BelongsToCurrentThread());
282 desktop_channel_
.reset();
284 if (desktop_process_
!= base::kNullProcessHandle
) {
285 base::CloseProcessHandle(desktop_process_
);
286 desktop_process_
= base::kNullProcessHandle
;
289 shared_buffers_
.clear();
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
>());
298 void DesktopSessionProxy::SetAudioCapturer(
299 const base::WeakPtr
<IpcAudioCapturer
>& audio_capturer
) {
300 DCHECK(audio_capture_task_runner_
->BelongsToCurrentThread());
302 audio_capturer_
= audio_capturer
;
305 void DesktopSessionProxy::CaptureFrame() {
306 if (!caller_task_runner_
->BelongsToCurrentThread()) {
307 caller_task_runner_
->PostTask(
308 FROM_HERE
, base::Bind(&DesktopSessionProxy::CaptureFrame
, this));
312 if (desktop_channel_
) {
313 ++pending_capture_frame_requests_
;
314 SendToDesktop(new ChromotingNetworkDesktopMsg_CaptureFrame());
316 PostCaptureCompleted(scoped_ptr
<webrtc::DesktopFrame
>());
320 void DesktopSessionProxy::SetVideoCapturer(
321 const base::WeakPtr
<IpcVideoFrameCapturer
> video_capturer
) {
322 DCHECK(video_capture_task_runner_
->BelongsToCurrentThread());
324 video_capturer_
= video_capturer
;
327 void DesktopSessionProxy::SetMouseCursorMonitor(
328 const base::WeakPtr
<IpcMouseCursorMonitor
>& mouse_cursor_monitor
) {
329 DCHECK(video_capture_task_runner_
->BelongsToCurrentThread());
331 mouse_cursor_monitor_
= mouse_cursor_monitor
;
334 void DesktopSessionProxy::DisconnectSession() {
335 DCHECK(caller_task_runner_
->BelongsToCurrentThread());
337 // Disconnect the client session if it hasn't been disconnected yet.
338 if (client_session_control_
.get())
339 client_session_control_
->DisconnectSession();
342 void DesktopSessionProxy::InjectClipboardEvent(
343 const protocol::ClipboardEvent
& event
) {
344 DCHECK(caller_task_runner_
->BelongsToCurrentThread());
346 std::string serialized_event
;
347 if (!event
.SerializeToString(&serialized_event
)) {
348 LOG(ERROR
) << "Failed to serialize protocol::ClipboardEvent.";
353 new ChromotingNetworkDesktopMsg_InjectClipboardEvent(serialized_event
));
356 void DesktopSessionProxy::InjectKeyEvent(const protocol::KeyEvent
& event
) {
357 DCHECK(caller_task_runner_
->BelongsToCurrentThread());
359 std::string serialized_event
;
360 if (!event
.SerializeToString(&serialized_event
)) {
361 LOG(ERROR
) << "Failed to serialize protocol::KeyEvent.";
366 new ChromotingNetworkDesktopMsg_InjectKeyEvent(serialized_event
));
369 void DesktopSessionProxy::InjectTextEvent(const protocol::TextEvent
& event
) {
370 DCHECK(caller_task_runner_
->BelongsToCurrentThread());
372 std::string serialized_event
;
373 if (!event
.SerializeToString(&serialized_event
)) {
374 LOG(ERROR
) << "Failed to serialize protocol::TextEvent.";
379 new ChromotingNetworkDesktopMsg_InjectTextEvent(serialized_event
));
382 void DesktopSessionProxy::InjectMouseEvent(const protocol::MouseEvent
& event
) {
383 DCHECK(caller_task_runner_
->BelongsToCurrentThread());
385 std::string serialized_event
;
386 if (!event
.SerializeToString(&serialized_event
)) {
387 LOG(ERROR
) << "Failed to serialize protocol::MouseEvent.";
392 new ChromotingNetworkDesktopMsg_InjectMouseEvent(serialized_event
));
395 void DesktopSessionProxy::StartInputInjector(
396 scoped_ptr
<protocol::ClipboardStub
> client_clipboard
) {
397 DCHECK(caller_task_runner_
->BelongsToCurrentThread());
399 client_clipboard_
= client_clipboard
.Pass();
402 void DesktopSessionProxy::SetScreenResolution(
403 const ScreenResolution
& resolution
) {
404 DCHECK(caller_task_runner_
->BelongsToCurrentThread());
406 if (resolution
.IsEmpty())
409 screen_resolution_
= resolution
;
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_
);
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_
);
428 new ChromotingNetworkDesktopMsg_SetScreenResolution(screen_resolution_
));
431 DesktopSessionProxy::~DesktopSessionProxy() {
432 DCHECK(caller_task_runner_
->BelongsToCurrentThread());
434 if (desktop_session_connector_
.get() && is_desktop_session_connected_
)
435 desktop_session_connector_
->DisconnectTerminal(this);
437 if (desktop_process_
!= base::kNullProcessHandle
) {
438 base::CloseProcessHandle(desktop_process_
);
439 desktop_process_
= base::kNullProcessHandle
;
443 scoped_refptr
<DesktopSessionProxy::IpcSharedBufferCore
>
444 DesktopSessionProxy::GetSharedBufferCore(int id
) {
445 DCHECK(caller_task_runner_
->BelongsToCurrentThread());
447 SharedBuffers::const_iterator i
= shared_buffers_
.find(id
);
448 if (i
!= shared_buffers_
.end()) {
451 LOG(ERROR
) << "Failed to find the shared buffer " << id
;
456 void DesktopSessionProxy::OnAudioPacket(const std::string
& serialized_packet
) {
457 DCHECK(caller_task_runner_
->BelongsToCurrentThread());
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.";
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
)));
473 void DesktopSessionProxy::OnCreateSharedBuffer(
475 IPC::PlatformFileForTransit handle
,
477 DCHECK(caller_task_runner_
->BelongsToCurrentThread());
479 scoped_refptr
<IpcSharedBufferCore
> shared_buffer
=
480 new IpcSharedBufferCore(id
, handle
, desktop_process_
, size
);
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";
488 void DesktopSessionProxy::OnReleaseSharedBuffer(int id
) {
489 DCHECK(caller_task_runner_
->BelongsToCurrentThread());
491 // Drop the cached reference to the buffer.
492 shared_buffers_
.erase(id
);
495 void DesktopSessionProxy::OnCaptureCompleted(
496 const SerializedDesktopFrame
& serialized_frame
) {
497 DCHECK(caller_task_runner_
->BelongsToCurrentThread());
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());
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
);
512 for (size_t i
= 0; i
< serialized_frame
.dirty_region
.size(); ++i
) {
513 frame
->mutable_updated_region()->AddRect(serialized_frame
.dirty_region
[i
]);
516 --pending_capture_frame_requests_
;
517 PostCaptureCompleted(frame
.Pass());
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());
528 void DesktopSessionProxy::OnInjectClipboardEvent(
529 const std::string
& serialized_event
) {
530 DCHECK(caller_task_runner_
->BelongsToCurrentThread());
532 if (client_clipboard_
) {
533 protocol::ClipboardEvent event
;
534 if (!event
.ParseFromString(serialized_event
)) {
535 LOG(ERROR
) << "Failed to parse protocol::ClipboardEvent.";
539 client_clipboard_
->InjectClipboardEvent(event
);
543 void DesktopSessionProxy::PostCaptureCompleted(
544 scoped_ptr
<webrtc::DesktopFrame
> frame
) {
545 DCHECK(caller_task_runner_
->BelongsToCurrentThread());
547 video_capture_task_runner_
->PostTask(
549 base::Bind(&IpcVideoFrameCapturer::OnCaptureCompleted
, video_capturer_
,
550 base::Passed(&frame
)));
553 void DesktopSessionProxy::PostMouseCursor(
554 scoped_ptr
<webrtc::MouseCursor
> mouse_cursor
) {
555 DCHECK(caller_task_runner_
->BelongsToCurrentThread());
557 video_capture_task_runner_
->PostTask(
559 base::Bind(&IpcMouseCursorMonitor::OnMouseCursor
, mouse_cursor_monitor_
,
560 base::Passed(&mouse_cursor
)));
563 void DesktopSessionProxy::SendToDesktop(IPC::Message
* message
) {
564 DCHECK(caller_task_runner_
->BelongsToCurrentThread());
566 if (desktop_channel_
) {
567 desktop_channel_
->Send(message
);
574 void DesktopSessionProxyTraits::Destruct(
575 const DesktopSessionProxy
* desktop_session_proxy
) {
576 desktop_session_proxy
->caller_task_runner_
->DeleteSoon(FROM_HERE
,
577 desktop_session_proxy
);
580 } // namespace remoting