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_win.h"
10 #include "base/base_switches.h"
11 #include "base/command_line.h"
12 #include "base/files/file_path.h"
13 #include "base/guid.h"
14 #include "base/memory/ref_counted.h"
15 #include "base/memory/scoped_ptr.h"
16 #include "base/memory/weak_ptr.h"
17 #include "base/path_service.h"
18 #include "base/strings/stringprintf.h"
19 #include "base/strings/utf_string_conversions.h"
20 #include "base/threading/thread_checker.h"
21 #include "base/timer/timer.h"
22 #include "base/win/scoped_bstr.h"
23 #include "base/win/scoped_comptr.h"
24 #include "base/win/scoped_handle.h"
25 #include "base/win/windows_version.h"
26 #include "ipc/ipc_message_macros.h"
27 #include "ipc/ipc_platform_file.h"
28 #include "remoting/base/auto_thread_task_runner.h"
29 // MIDL-generated declarations and definitions.
30 #include "remoting/host/chromoting_lib.h"
31 #include "remoting/host/chromoting_messages.h"
32 #include "remoting/host/daemon_process.h"
33 #include "remoting/host/desktop_session.h"
34 #include "remoting/host/host_main.h"
35 #include "remoting/host/ipc_constants.h"
36 #include "remoting/host/sas_injector.h"
37 #include "remoting/host/screen_resolution.h"
38 #include "remoting/host/win/host_service.h"
39 #include "remoting/host/win/worker_process_launcher.h"
40 #include "remoting/host/win/wts_session_process_delegate.h"
41 #include "remoting/host/win/wts_terminal_monitor.h"
42 #include "remoting/host/win/wts_terminal_observer.h"
43 #include "remoting/host/worker_process_ipc_delegate.h"
45 using base::win::ScopedHandle
;
51 // The security descriptor of the daemon IPC endpoint. It gives full access
52 // to SYSTEM and denies access by anyone else.
53 const wchar_t kDaemonIpcSecurityDescriptor
[] =
54 SDDL_OWNER L
":" SDDL_LOCAL_SYSTEM
55 SDDL_GROUP L
":" SDDL_LOCAL_SYSTEM
57 SDDL_ACCESS_ALLOWED L
";;" SDDL_GENERIC_ALL L
";;;" SDDL_LOCAL_SYSTEM
60 // The command line parameters that should be copied from the service's command
61 // line to the desktop process.
62 const char* kCopiedSwitchNames
[] = { switches::kV
, switches::kVModule
};
64 // The default screen dimensions for an RDP session.
65 const int kDefaultRdpScreenWidth
= 1280;
66 const int kDefaultRdpScreenHeight
= 768;
68 // RDC 6.1 (W2K8) supports dimensions of up to 4096x2048.
69 const int kMaxRdpScreenWidth
= 4096;
70 const int kMaxRdpScreenHeight
= 2048;
72 // The minimum effective screen dimensions supported by Windows are 800x600.
73 const int kMinRdpScreenWidth
= 800;
74 const int kMinRdpScreenHeight
= 600;
76 // Default dots per inch used by RDP is 96 DPI.
77 const int kDefaultRdpDpi
= 96;
79 // The session attach notification should arrive within 30 seconds.
80 const int kSessionAttachTimeoutSeconds
= 30;
82 // DesktopSession implementation which attaches to the host's physical console.
83 // Receives IPC messages from the desktop process, running in the console
84 // session, via |WorkerProcessIpcDelegate|, and monitors console session
85 // attach/detach events via |WtsConsoleObserer|.
86 class ConsoleSession
: public DesktopSessionWin
{
88 // Same as DesktopSessionWin().
90 scoped_refptr
<AutoThreadTaskRunner
> caller_task_runner
,
91 scoped_refptr
<AutoThreadTaskRunner
> io_task_runner
,
92 DaemonProcess
* daemon_process
,
94 WtsTerminalMonitor
* monitor
);
95 virtual ~ConsoleSession();
98 // DesktopSession overrides.
99 virtual void SetScreenResolution(const ScreenResolution
& resolution
) OVERRIDE
;
101 // DesktopSessionWin overrides.
102 virtual void InjectSas() OVERRIDE
;
105 scoped_ptr
<SasInjector
> sas_injector_
;
107 DISALLOW_COPY_AND_ASSIGN(ConsoleSession
);
110 // DesktopSession implementation which attaches to virtual RDP console.
111 // Receives IPC messages from the desktop process, running in the console
112 // session, via |WorkerProcessIpcDelegate|, and monitors console session
113 // attach/detach events via |WtsConsoleObserer|.
114 class RdpSession
: public DesktopSessionWin
{
116 // Same as DesktopSessionWin().
118 scoped_refptr
<AutoThreadTaskRunner
> caller_task_runner
,
119 scoped_refptr
<AutoThreadTaskRunner
> io_task_runner
,
120 DaemonProcess
* daemon_process
,
122 WtsTerminalMonitor
* monitor
);
123 virtual ~RdpSession();
125 // Performs the part of initialization that can fail.
126 bool Initialize(const ScreenResolution
& resolution
);
128 // Mirrors IRdpDesktopSessionEventHandler.
129 void OnRdpConnected();
133 // DesktopSession overrides.
134 virtual void SetScreenResolution(const ScreenResolution
& resolution
) OVERRIDE
;
136 // DesktopSessionWin overrides.
137 virtual void InjectSas() OVERRIDE
;
140 // An implementation of IRdpDesktopSessionEventHandler interface that forwards
141 // notifications to the owning desktop session.
142 class EventHandler
: public IRdpDesktopSessionEventHandler
{
144 explicit EventHandler(base::WeakPtr
<RdpSession
> desktop_session
);
145 virtual ~EventHandler();
147 // IUnknown interface.
148 STDMETHOD_(ULONG
, AddRef
)() OVERRIDE
;
149 STDMETHOD_(ULONG
, Release
)() OVERRIDE
;
150 STDMETHOD(QueryInterface
)(REFIID riid
, void** ppv
) OVERRIDE
;
152 // IRdpDesktopSessionEventHandler interface.
153 STDMETHOD(OnRdpConnected
)() OVERRIDE
;
154 STDMETHOD(OnRdpClosed
)() OVERRIDE
;
159 // Points to the desktop session object receiving OnRdpXxx() notifications.
160 base::WeakPtr
<RdpSession
> desktop_session_
;
162 // This class must be used on a single thread.
163 base::ThreadChecker thread_checker_
;
165 DISALLOW_COPY_AND_ASSIGN(EventHandler
);
168 // Used to create an RDP desktop session.
169 base::win::ScopedComPtr
<IRdpDesktopSession
> rdp_desktop_session_
;
171 // Used to match |rdp_desktop_session_| with the session it is attached to.
172 std::string terminal_id_
;
174 base::WeakPtrFactory
<RdpSession
> weak_factory_
;
176 DISALLOW_COPY_AND_ASSIGN(RdpSession
);
179 ConsoleSession::ConsoleSession(
180 scoped_refptr
<AutoThreadTaskRunner
> caller_task_runner
,
181 scoped_refptr
<AutoThreadTaskRunner
> io_task_runner
,
182 DaemonProcess
* daemon_process
,
184 WtsTerminalMonitor
* monitor
)
185 : DesktopSessionWin(caller_task_runner
, io_task_runner
, daemon_process
, id
,
187 StartMonitoring(WtsTerminalMonitor::kConsole
);
190 ConsoleSession::~ConsoleSession() {
193 void ConsoleSession::SetScreenResolution(const ScreenResolution
& resolution
) {
194 // Do nothing. The screen resolution of the console session is controlled by
195 // the DesktopSessionAgent instance running in that session.
196 DCHECK(caller_task_runner()->BelongsToCurrentThread());
199 void ConsoleSession::InjectSas() {
200 DCHECK(caller_task_runner()->BelongsToCurrentThread());
203 sas_injector_
= SasInjector::Create();
204 if (!sas_injector_
->InjectSas())
205 LOG(ERROR
) << "Failed to inject Secure Attention Sequence.";
208 RdpSession::RdpSession(
209 scoped_refptr
<AutoThreadTaskRunner
> caller_task_runner
,
210 scoped_refptr
<AutoThreadTaskRunner
> io_task_runner
,
211 DaemonProcess
* daemon_process
,
213 WtsTerminalMonitor
* monitor
)
214 : DesktopSessionWin(caller_task_runner
, io_task_runner
, daemon_process
, id
,
216 weak_factory_(this) {
219 RdpSession::~RdpSession() {
222 bool RdpSession::Initialize(const ScreenResolution
& resolution
) {
223 DCHECK(caller_task_runner()->BelongsToCurrentThread());
225 // Create the RDP wrapper object.
226 HRESULT result
= rdp_desktop_session_
.CreateInstance(
227 __uuidof(RdpDesktopSession
));
228 if (FAILED(result
)) {
229 LOG(ERROR
) << "Failed to create RdpSession object, 0x"
230 << std::hex
<< result
<< std::dec
<< ".";
234 ScreenResolution local_resolution
= resolution
;
236 // If the screen resolution is not specified, use the default screen
238 if (local_resolution
.IsEmpty()) {
239 local_resolution
= ScreenResolution(
240 webrtc::DesktopSize(kDefaultRdpScreenWidth
, kDefaultRdpScreenHeight
),
241 webrtc::DesktopVector(kDefaultRdpDpi
, kDefaultRdpDpi
));
244 // Get the screen dimensions assuming the default DPI.
245 webrtc::DesktopSize host_size
= local_resolution
.ScaleDimensionsToDpi(
246 webrtc::DesktopVector(kDefaultRdpDpi
, kDefaultRdpDpi
));
248 // Make sure that the host resolution is within the limits supported by RDP.
249 host_size
= webrtc::DesktopSize(
250 std::min(kMaxRdpScreenWidth
,
251 std::max(kMinRdpScreenWidth
, host_size
.width())),
252 std::min(kMaxRdpScreenHeight
,
253 std::max(kMinRdpScreenHeight
, host_size
.height())));
255 // Create an RDP session.
256 base::win::ScopedComPtr
<IRdpDesktopSessionEventHandler
> event_handler(
257 new EventHandler(weak_factory_
.GetWeakPtr()));
258 terminal_id_
= base::GenerateGUID();
259 base::win::ScopedBstr
terminal_id(base::UTF8ToUTF16(terminal_id_
).c_str());
260 result
= rdp_desktop_session_
->Connect(host_size
.width(),
264 if (FAILED(result
)) {
265 LOG(ERROR
) << "RdpSession::Create() failed, 0x"
266 << std::hex
<< result
<< std::dec
<< ".";
273 void RdpSession::OnRdpConnected() {
274 DCHECK(caller_task_runner()->BelongsToCurrentThread());
277 StartMonitoring(terminal_id_
);
280 void RdpSession::OnRdpClosed() {
281 DCHECK(caller_task_runner()->BelongsToCurrentThread());
286 void RdpSession::SetScreenResolution(const ScreenResolution
& resolution
) {
287 DCHECK(caller_task_runner()->BelongsToCurrentThread());
289 // TODO(alexeypa): implement resize-to-client for RDP sessions here.
290 // See http://crbug.com/137696.
294 void RdpSession::InjectSas() {
295 DCHECK(caller_task_runner()->BelongsToCurrentThread());
297 rdp_desktop_session_
->InjectSas();
300 RdpSession::EventHandler::EventHandler(
301 base::WeakPtr
<RdpSession
> desktop_session
)
303 desktop_session_(desktop_session
) {
306 RdpSession::EventHandler::~EventHandler() {
307 DCHECK(thread_checker_
.CalledOnValidThread());
309 if (desktop_session_
)
310 desktop_session_
->OnRdpClosed();
313 ULONG STDMETHODCALLTYPE
RdpSession::EventHandler::AddRef() {
314 DCHECK(thread_checker_
.CalledOnValidThread());
319 ULONG STDMETHODCALLTYPE
RdpSession::EventHandler::Release() {
320 DCHECK(thread_checker_
.CalledOnValidThread());
322 if (--ref_count_
== 0) {
330 STDMETHODIMP
RdpSession::EventHandler::QueryInterface(REFIID riid
, void** ppv
) {
331 DCHECK(thread_checker_
.CalledOnValidThread());
333 if (riid
== IID_IUnknown
||
334 riid
== IID_IRdpDesktopSessionEventHandler
) {
335 *ppv
= static_cast<IRdpDesktopSessionEventHandler
*>(this);
341 return E_NOINTERFACE
;
344 STDMETHODIMP
RdpSession::EventHandler::OnRdpConnected() {
345 DCHECK(thread_checker_
.CalledOnValidThread());
347 if (desktop_session_
)
348 desktop_session_
->OnRdpConnected();
353 STDMETHODIMP
RdpSession::EventHandler::OnRdpClosed() {
354 DCHECK(thread_checker_
.CalledOnValidThread());
356 if (!desktop_session_
)
359 base::WeakPtr
<RdpSession
> desktop_session
= desktop_session_
;
360 desktop_session_
.reset();
361 desktop_session
->OnRdpClosed();
368 scoped_ptr
<DesktopSession
> DesktopSessionWin::CreateForConsole(
369 scoped_refptr
<AutoThreadTaskRunner
> caller_task_runner
,
370 scoped_refptr
<AutoThreadTaskRunner
> io_task_runner
,
371 DaemonProcess
* daemon_process
,
373 const ScreenResolution
& resolution
) {
374 scoped_ptr
<ConsoleSession
> session(new ConsoleSession(
375 caller_task_runner
, io_task_runner
, daemon_process
, id
,
376 HostService::GetInstance()));
378 return session
.PassAs
<DesktopSession
>();
382 scoped_ptr
<DesktopSession
> DesktopSessionWin::CreateForVirtualTerminal(
383 scoped_refptr
<AutoThreadTaskRunner
> caller_task_runner
,
384 scoped_refptr
<AutoThreadTaskRunner
> io_task_runner
,
385 DaemonProcess
* daemon_process
,
387 const ScreenResolution
& resolution
) {
388 scoped_ptr
<RdpSession
> session(new RdpSession(
389 caller_task_runner
, io_task_runner
, daemon_process
, id
,
390 HostService::GetInstance()));
391 if (!session
->Initialize(resolution
))
392 return scoped_ptr
<DesktopSession
>();
394 return session
.PassAs
<DesktopSession
>();
397 DesktopSessionWin::DesktopSessionWin(
398 scoped_refptr
<AutoThreadTaskRunner
> caller_task_runner
,
399 scoped_refptr
<AutoThreadTaskRunner
> io_task_runner
,
400 DaemonProcess
* daemon_process
,
402 WtsTerminalMonitor
* monitor
)
403 : DesktopSession(daemon_process
, id
),
404 caller_task_runner_(caller_task_runner
),
405 io_task_runner_(io_task_runner
),
407 monitoring_notifications_(false) {
408 DCHECK(caller_task_runner_
->BelongsToCurrentThread());
410 ReportElapsedTime("created");
413 DesktopSessionWin::~DesktopSessionWin() {
414 DCHECK(caller_task_runner_
->BelongsToCurrentThread());
419 void DesktopSessionWin::OnSessionAttachTimeout() {
420 DCHECK(caller_task_runner_
->BelongsToCurrentThread());
422 LOG(ERROR
) << "Session attach notification didn't arrived within "
423 << kSessionAttachTimeoutSeconds
<< " seconds.";
427 void DesktopSessionWin::StartMonitoring(const std::string
& terminal_id
) {
428 DCHECK(caller_task_runner_
->BelongsToCurrentThread());
429 DCHECK(!monitoring_notifications_
);
430 DCHECK(!session_attach_timer_
.IsRunning());
432 ReportElapsedTime("started monitoring");
434 session_attach_timer_
.Start(
435 FROM_HERE
, base::TimeDelta::FromSeconds(kSessionAttachTimeoutSeconds
),
436 this, &DesktopSessionWin::OnSessionAttachTimeout
);
438 monitoring_notifications_
= true;
439 monitor_
->AddWtsTerminalObserver(terminal_id
, this);
442 void DesktopSessionWin::StopMonitoring() {
443 DCHECK(caller_task_runner_
->BelongsToCurrentThread());
445 if (monitoring_notifications_
) {
446 ReportElapsedTime("stopped monitoring");
448 monitoring_notifications_
= false;
449 monitor_
->RemoveWtsTerminalObserver(this);
452 session_attach_timer_
.Stop();
456 void DesktopSessionWin::TerminateSession() {
457 DCHECK(caller_task_runner_
->BelongsToCurrentThread());
461 // This call will delete |this| so it should be at the very end of the method.
462 daemon_process()->CloseDesktopSession(id());
465 void DesktopSessionWin::OnChannelConnected(int32 peer_pid
) {
466 DCHECK(caller_task_runner_
->BelongsToCurrentThread());
468 ReportElapsedTime("channel connected");
470 // Obtain the handle of the desktop process. It will be passed to the network
471 // process to use to duplicate handles of shared memory objects from
472 // the desktop process.
473 desktop_process_
.Set(OpenProcess(PROCESS_DUP_HANDLE
, false, peer_pid
));
474 if (!desktop_process_
.IsValid()) {
475 CrashDesktopProcess(FROM_HERE
);
479 VLOG(1) << "IPC: daemon <- desktop (" << peer_pid
<< ")";
482 bool DesktopSessionWin::OnMessageReceived(const IPC::Message
& message
) {
483 DCHECK(caller_task_runner_
->BelongsToCurrentThread());
486 IPC_BEGIN_MESSAGE_MAP(DesktopSessionWin
, message
)
487 IPC_MESSAGE_HANDLER(ChromotingDesktopDaemonMsg_DesktopAttached
,
488 OnDesktopSessionAgentAttached
)
489 IPC_MESSAGE_HANDLER(ChromotingDesktopDaemonMsg_InjectSas
,
491 IPC_MESSAGE_UNHANDLED(handled
= false)
492 IPC_END_MESSAGE_MAP()
495 LOG(ERROR
) << "Received unexpected IPC type: " << message
.type();
496 CrashDesktopProcess(FROM_HERE
);
502 void DesktopSessionWin::OnPermanentError(int exit_code
) {
503 DCHECK(caller_task_runner_
->BelongsToCurrentThread());
508 void DesktopSessionWin::OnSessionAttached(uint32 session_id
) {
509 DCHECK(caller_task_runner_
->BelongsToCurrentThread());
511 DCHECK(monitoring_notifications_
);
513 ReportElapsedTime("attached");
515 // Launch elevated on Win8 to be able to inject Alt+Tab.
516 bool launch_elevated
= base::win::GetVersion() >= base::win::VERSION_WIN8
;
518 // Get the name of the executable to run. |kDesktopBinaryName| specifies
519 // uiAccess="true" in it's manifest.
520 base::FilePath desktop_binary
;
522 if (launch_elevated
) {
523 result
= GetInstalledBinaryPath(kDesktopBinaryName
, &desktop_binary
);
525 result
= GetInstalledBinaryPath(kHostBinaryName
, &desktop_binary
);
533 session_attach_timer_
.Stop();
535 scoped_ptr
<CommandLine
> target(new CommandLine(desktop_binary
));
536 target
->AppendSwitchASCII(kProcessTypeSwitchName
, kProcessTypeDesktop
);
537 // Copy the command line switches enabling verbose logging.
538 target
->CopySwitchesFrom(*CommandLine::ForCurrentProcess(),
540 arraysize(kCopiedSwitchNames
));
542 // Create a delegate capable of launching a process in a different session.
543 scoped_ptr
<WtsSessionProcessDelegate
> delegate(
544 new WtsSessionProcessDelegate(io_task_runner_
,
548 kDaemonIpcSecurityDescriptor
)));
549 if (!delegate
->Initialize(session_id
)) {
554 // Create a launcher for the desktop process, using the per-session delegate.
555 launcher_
.reset(new WorkerProcessLauncher(
556 delegate
.PassAs
<WorkerProcessLauncher::Delegate
>(), this));
559 void DesktopSessionWin::OnSessionDetached() {
560 DCHECK(caller_task_runner_
->BelongsToCurrentThread());
564 if (monitoring_notifications_
) {
565 ReportElapsedTime("detached");
567 session_attach_timer_
.Start(
568 FROM_HERE
, base::TimeDelta::FromSeconds(kSessionAttachTimeoutSeconds
),
569 this, &DesktopSessionWin::OnSessionAttachTimeout
);
573 void DesktopSessionWin::OnDesktopSessionAgentAttached(
574 IPC::PlatformFileForTransit desktop_pipe
) {
575 if (!daemon_process()->OnDesktopSessionAgentAttached(id(),
578 CrashDesktopProcess(FROM_HERE
);
582 void DesktopSessionWin::CrashDesktopProcess(
583 const tracked_objects::Location
& location
) {
584 DCHECK(caller_task_runner_
->BelongsToCurrentThread());
586 launcher_
->Crash(location
);
589 void DesktopSessionWin::ReportElapsedTime(const std::string
& event
) {
590 base::Time now
= base::Time::Now();
593 if (!last_timestamp_
.is_null()) {
594 passed
= base::StringPrintf(", %.2fs passed",
595 (now
- last_timestamp_
).InSecondsF());
598 base::Time::Exploded exploded
;
599 now
.LocalExplode(&exploded
);
600 VLOG(1) << base::StringPrintf("session(%d): %s at %02d:%02d:%02d.%03d%s",
606 exploded
.millisecond
,
609 last_timestamp_
= now
;
612 } // namespace remoting