• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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_win.h"
6 
7 #include <limits>
8 #include <sddl.h>
9 
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"
44 
45 using base::win::ScopedHandle;
46 
47 namespace remoting {
48 
49 namespace {
50 
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
56     SDDL_DACL L":("
57         SDDL_ACCESS_ALLOWED L";;" SDDL_GENERIC_ALL L";;;" SDDL_LOCAL_SYSTEM
58     L")";
59 
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 };
63 
64 // The default screen dimensions for an RDP session.
65 const int kDefaultRdpScreenWidth = 1280;
66 const int kDefaultRdpScreenHeight = 768;
67 
68 // RDC 6.1 (W2K8) supports dimensions of up to 4096x2048.
69 const int kMaxRdpScreenWidth = 4096;
70 const int kMaxRdpScreenHeight = 2048;
71 
72 // The minimum effective screen dimensions supported by Windows are 800x600.
73 const int kMinRdpScreenWidth = 800;
74 const int kMinRdpScreenHeight = 600;
75 
76 // Default dots per inch used by RDP is 96 DPI.
77 const int kDefaultRdpDpi = 96;
78 
79 // The session attach notification should arrive within 30 seconds.
80 const int kSessionAttachTimeoutSeconds = 30;
81 
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 {
87  public:
88   // Same as DesktopSessionWin().
89   ConsoleSession(
90     scoped_refptr<AutoThreadTaskRunner> caller_task_runner,
91     scoped_refptr<AutoThreadTaskRunner> io_task_runner,
92     DaemonProcess* daemon_process,
93     int id,
94     WtsTerminalMonitor* monitor);
95   virtual ~ConsoleSession();
96 
97  protected:
98   // DesktopSession overrides.
99   virtual void SetScreenResolution(const ScreenResolution& resolution) OVERRIDE;
100 
101   // DesktopSessionWin overrides.
102   virtual void InjectSas() OVERRIDE;
103 
104  private:
105   scoped_ptr<SasInjector> sas_injector_;
106 
107   DISALLOW_COPY_AND_ASSIGN(ConsoleSession);
108 };
109 
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 {
115  public:
116   // Same as DesktopSessionWin().
117   RdpSession(
118     scoped_refptr<AutoThreadTaskRunner> caller_task_runner,
119     scoped_refptr<AutoThreadTaskRunner> io_task_runner,
120     DaemonProcess* daemon_process,
121     int id,
122     WtsTerminalMonitor* monitor);
123   virtual ~RdpSession();
124 
125   // Performs the part of initialization that can fail.
126   bool Initialize(const ScreenResolution& resolution);
127 
128   // Mirrors IRdpDesktopSessionEventHandler.
129   void OnRdpConnected();
130   void OnRdpClosed();
131 
132  protected:
133   // DesktopSession overrides.
134   virtual void SetScreenResolution(const ScreenResolution& resolution) OVERRIDE;
135 
136   // DesktopSessionWin overrides.
137   virtual void InjectSas() OVERRIDE;
138 
139  private:
140   // An implementation of IRdpDesktopSessionEventHandler interface that forwards
141   // notifications to the owning desktop session.
142   class EventHandler : public IRdpDesktopSessionEventHandler {
143    public:
144     explicit EventHandler(base::WeakPtr<RdpSession> desktop_session);
145     virtual ~EventHandler();
146 
147     // IUnknown interface.
148     STDMETHOD_(ULONG, AddRef)() OVERRIDE;
149     STDMETHOD_(ULONG, Release)() OVERRIDE;
150     STDMETHOD(QueryInterface)(REFIID riid, void** ppv) OVERRIDE;
151 
152     // IRdpDesktopSessionEventHandler interface.
153     STDMETHOD(OnRdpConnected)() OVERRIDE;
154     STDMETHOD(OnRdpClosed)() OVERRIDE;
155 
156    private:
157     ULONG ref_count_;
158 
159     // Points to the desktop session object receiving OnRdpXxx() notifications.
160     base::WeakPtr<RdpSession> desktop_session_;
161 
162     // This class must be used on a single thread.
163     base::ThreadChecker thread_checker_;
164 
165     DISALLOW_COPY_AND_ASSIGN(EventHandler);
166   };
167 
168   // Used to create an RDP desktop session.
169   base::win::ScopedComPtr<IRdpDesktopSession> rdp_desktop_session_;
170 
171   // Used to match |rdp_desktop_session_| with the session it is attached to.
172   std::string terminal_id_;
173 
174   base::WeakPtrFactory<RdpSession> weak_factory_;
175 
176   DISALLOW_COPY_AND_ASSIGN(RdpSession);
177 };
178 
ConsoleSession(scoped_refptr<AutoThreadTaskRunner> caller_task_runner,scoped_refptr<AutoThreadTaskRunner> io_task_runner,DaemonProcess * daemon_process,int id,WtsTerminalMonitor * monitor)179 ConsoleSession::ConsoleSession(
180     scoped_refptr<AutoThreadTaskRunner> caller_task_runner,
181     scoped_refptr<AutoThreadTaskRunner> io_task_runner,
182     DaemonProcess* daemon_process,
183     int id,
184     WtsTerminalMonitor* monitor)
185     : DesktopSessionWin(caller_task_runner, io_task_runner, daemon_process, id,
186                         monitor) {
187   StartMonitoring(WtsTerminalMonitor::kConsole);
188 }
189 
~ConsoleSession()190 ConsoleSession::~ConsoleSession() {
191 }
192 
SetScreenResolution(const ScreenResolution & resolution)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());
197 }
198 
InjectSas()199 void ConsoleSession::InjectSas() {
200   DCHECK(caller_task_runner()->BelongsToCurrentThread());
201 
202   if (!sas_injector_)
203     sas_injector_ = SasInjector::Create();
204   if (!sas_injector_->InjectSas())
205     LOG(ERROR) << "Failed to inject Secure Attention Sequence.";
206 }
207 
RdpSession(scoped_refptr<AutoThreadTaskRunner> caller_task_runner,scoped_refptr<AutoThreadTaskRunner> io_task_runner,DaemonProcess * daemon_process,int id,WtsTerminalMonitor * monitor)208 RdpSession::RdpSession(
209     scoped_refptr<AutoThreadTaskRunner> caller_task_runner,
210     scoped_refptr<AutoThreadTaskRunner> io_task_runner,
211     DaemonProcess* daemon_process,
212     int id,
213     WtsTerminalMonitor* monitor)
214     : DesktopSessionWin(caller_task_runner, io_task_runner, daemon_process, id,
215                         monitor),
216       weak_factory_(this) {
217 }
218 
~RdpSession()219 RdpSession::~RdpSession() {
220 }
221 
Initialize(const ScreenResolution & resolution)222 bool RdpSession::Initialize(const ScreenResolution& resolution) {
223   DCHECK(caller_task_runner()->BelongsToCurrentThread());
224 
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 << ".";
231     return false;
232   }
233 
234   ScreenResolution local_resolution = resolution;
235 
236   // If the screen resolution is not specified, use the default screen
237   // resolution.
238   if (local_resolution.IsEmpty()) {
239     local_resolution = ScreenResolution(
240         webrtc::DesktopSize(kDefaultRdpScreenWidth, kDefaultRdpScreenHeight),
241         webrtc::DesktopVector(kDefaultRdpDpi, kDefaultRdpDpi));
242   }
243 
244   // Get the screen dimensions assuming the default DPI.
245   webrtc::DesktopSize host_size = local_resolution.ScaleDimensionsToDpi(
246       webrtc::DesktopVector(kDefaultRdpDpi, kDefaultRdpDpi));
247 
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())));
254 
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(),
261                                          host_size.height(),
262                                          terminal_id,
263                                          event_handler);
264   if (FAILED(result)) {
265     LOG(ERROR) << "RdpSession::Create() failed, 0x"
266                << std::hex << result << std::dec << ".";
267     return false;
268   }
269 
270   return true;
271 }
272 
OnRdpConnected()273 void RdpSession::OnRdpConnected() {
274   DCHECK(caller_task_runner()->BelongsToCurrentThread());
275 
276   StopMonitoring();
277   StartMonitoring(terminal_id_);
278 }
279 
OnRdpClosed()280 void RdpSession::OnRdpClosed() {
281   DCHECK(caller_task_runner()->BelongsToCurrentThread());
282 
283   TerminateSession();
284 }
285 
SetScreenResolution(const ScreenResolution & resolution)286 void RdpSession::SetScreenResolution(const ScreenResolution& resolution) {
287   DCHECK(caller_task_runner()->BelongsToCurrentThread());
288 
289   // TODO(alexeypa): implement resize-to-client for RDP sessions here.
290   // See http://crbug.com/137696.
291   NOTIMPLEMENTED();
292 }
293 
InjectSas()294 void RdpSession::InjectSas() {
295   DCHECK(caller_task_runner()->BelongsToCurrentThread());
296 
297   rdp_desktop_session_->InjectSas();
298 }
299 
EventHandler(base::WeakPtr<RdpSession> desktop_session)300 RdpSession::EventHandler::EventHandler(
301     base::WeakPtr<RdpSession> desktop_session)
302     : ref_count_(0),
303       desktop_session_(desktop_session) {
304 }
305 
~EventHandler()306 RdpSession::EventHandler::~EventHandler() {
307   DCHECK(thread_checker_.CalledOnValidThread());
308 
309   if (desktop_session_)
310     desktop_session_->OnRdpClosed();
311 }
312 
AddRef()313 ULONG STDMETHODCALLTYPE RdpSession::EventHandler::AddRef() {
314   DCHECK(thread_checker_.CalledOnValidThread());
315 
316   return ++ref_count_;
317 }
318 
Release()319 ULONG STDMETHODCALLTYPE RdpSession::EventHandler::Release() {
320   DCHECK(thread_checker_.CalledOnValidThread());
321 
322   if (--ref_count_ == 0) {
323     delete this;
324     return 0;
325   }
326 
327   return ref_count_;
328 }
329 
QueryInterface(REFIID riid,void ** ppv)330 STDMETHODIMP RdpSession::EventHandler::QueryInterface(REFIID riid, void** ppv) {
331   DCHECK(thread_checker_.CalledOnValidThread());
332 
333   if (riid == IID_IUnknown ||
334       riid == IID_IRdpDesktopSessionEventHandler) {
335     *ppv = static_cast<IRdpDesktopSessionEventHandler*>(this);
336     AddRef();
337     return S_OK;
338   }
339 
340   *ppv = NULL;
341   return E_NOINTERFACE;
342 }
343 
OnRdpConnected()344 STDMETHODIMP RdpSession::EventHandler::OnRdpConnected() {
345   DCHECK(thread_checker_.CalledOnValidThread());
346 
347   if (desktop_session_)
348     desktop_session_->OnRdpConnected();
349 
350   return S_OK;
351 }
352 
OnRdpClosed()353 STDMETHODIMP RdpSession::EventHandler::OnRdpClosed() {
354   DCHECK(thread_checker_.CalledOnValidThread());
355 
356   if (!desktop_session_)
357     return S_OK;
358 
359   base::WeakPtr<RdpSession> desktop_session = desktop_session_;
360   desktop_session_.reset();
361   desktop_session->OnRdpClosed();
362   return S_OK;
363 }
364 
365 } // namespace
366 
367 // static
CreateForConsole(scoped_refptr<AutoThreadTaskRunner> caller_task_runner,scoped_refptr<AutoThreadTaskRunner> io_task_runner,DaemonProcess * daemon_process,int id,const ScreenResolution & resolution)368 scoped_ptr<DesktopSession> DesktopSessionWin::CreateForConsole(
369     scoped_refptr<AutoThreadTaskRunner> caller_task_runner,
370     scoped_refptr<AutoThreadTaskRunner> io_task_runner,
371     DaemonProcess* daemon_process,
372     int id,
373     const ScreenResolution& resolution) {
374   scoped_ptr<ConsoleSession> session(new ConsoleSession(
375       caller_task_runner, io_task_runner, daemon_process, id,
376       HostService::GetInstance()));
377 
378   return session.PassAs<DesktopSession>();
379 }
380 
381 // static
CreateForVirtualTerminal(scoped_refptr<AutoThreadTaskRunner> caller_task_runner,scoped_refptr<AutoThreadTaskRunner> io_task_runner,DaemonProcess * daemon_process,int id,const ScreenResolution & resolution)382 scoped_ptr<DesktopSession> DesktopSessionWin::CreateForVirtualTerminal(
383     scoped_refptr<AutoThreadTaskRunner> caller_task_runner,
384     scoped_refptr<AutoThreadTaskRunner> io_task_runner,
385     DaemonProcess* daemon_process,
386     int id,
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>();
393 
394   return session.PassAs<DesktopSession>();
395 }
396 
DesktopSessionWin(scoped_refptr<AutoThreadTaskRunner> caller_task_runner,scoped_refptr<AutoThreadTaskRunner> io_task_runner,DaemonProcess * daemon_process,int id,WtsTerminalMonitor * monitor)397 DesktopSessionWin::DesktopSessionWin(
398     scoped_refptr<AutoThreadTaskRunner> caller_task_runner,
399     scoped_refptr<AutoThreadTaskRunner> io_task_runner,
400     DaemonProcess* daemon_process,
401     int id,
402     WtsTerminalMonitor* monitor)
403     : DesktopSession(daemon_process, id),
404       caller_task_runner_(caller_task_runner),
405       io_task_runner_(io_task_runner),
406       monitor_(monitor),
407       monitoring_notifications_(false) {
408   DCHECK(caller_task_runner_->BelongsToCurrentThread());
409 
410   ReportElapsedTime("created");
411 }
412 
~DesktopSessionWin()413 DesktopSessionWin::~DesktopSessionWin() {
414   DCHECK(caller_task_runner_->BelongsToCurrentThread());
415 
416   StopMonitoring();
417 }
418 
OnSessionAttachTimeout()419 void DesktopSessionWin::OnSessionAttachTimeout() {
420   DCHECK(caller_task_runner_->BelongsToCurrentThread());
421 
422   LOG(ERROR) << "Session attach notification didn't arrived within "
423              << kSessionAttachTimeoutSeconds << " seconds.";
424   TerminateSession();
425 }
426 
StartMonitoring(const std::string & terminal_id)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());
431 
432   ReportElapsedTime("started monitoring");
433 
434   session_attach_timer_.Start(
435       FROM_HERE, base::TimeDelta::FromSeconds(kSessionAttachTimeoutSeconds),
436       this, &DesktopSessionWin::OnSessionAttachTimeout);
437 
438   monitoring_notifications_ = true;
439   monitor_->AddWtsTerminalObserver(terminal_id, this);
440 }
441 
StopMonitoring()442 void DesktopSessionWin::StopMonitoring() {
443   DCHECK(caller_task_runner_->BelongsToCurrentThread());
444 
445   if (monitoring_notifications_) {
446     ReportElapsedTime("stopped monitoring");
447 
448     monitoring_notifications_ = false;
449     monitor_->RemoveWtsTerminalObserver(this);
450   }
451 
452   session_attach_timer_.Stop();
453   OnSessionDetached();
454 }
455 
TerminateSession()456 void DesktopSessionWin::TerminateSession() {
457   DCHECK(caller_task_runner_->BelongsToCurrentThread());
458 
459   StopMonitoring();
460 
461   // This call will delete |this| so it should be at the very end of the method.
462   daemon_process()->CloseDesktopSession(id());
463 }
464 
OnChannelConnected(int32 peer_pid)465 void DesktopSessionWin::OnChannelConnected(int32 peer_pid) {
466   DCHECK(caller_task_runner_->BelongsToCurrentThread());
467 
468   ReportElapsedTime("channel connected");
469 
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);
476     return;
477   }
478 
479   VLOG(1) << "IPC: daemon <- desktop (" << peer_pid << ")";
480 }
481 
OnMessageReceived(const IPC::Message & message)482 bool DesktopSessionWin::OnMessageReceived(const IPC::Message& message) {
483   DCHECK(caller_task_runner_->BelongsToCurrentThread());
484 
485   bool handled = true;
486   IPC_BEGIN_MESSAGE_MAP(DesktopSessionWin, message)
487     IPC_MESSAGE_HANDLER(ChromotingDesktopDaemonMsg_DesktopAttached,
488                         OnDesktopSessionAgentAttached)
489     IPC_MESSAGE_HANDLER(ChromotingDesktopDaemonMsg_InjectSas,
490                         InjectSas)
491     IPC_MESSAGE_UNHANDLED(handled = false)
492   IPC_END_MESSAGE_MAP()
493 
494   if (!handled) {
495     LOG(ERROR) << "Received unexpected IPC type: " << message.type();
496     CrashDesktopProcess(FROM_HERE);
497   }
498 
499   return handled;
500 }
501 
OnPermanentError(int exit_code)502 void DesktopSessionWin::OnPermanentError(int exit_code) {
503   DCHECK(caller_task_runner_->BelongsToCurrentThread());
504 
505   TerminateSession();
506 }
507 
OnSessionAttached(uint32 session_id)508 void DesktopSessionWin::OnSessionAttached(uint32 session_id) {
509   DCHECK(caller_task_runner_->BelongsToCurrentThread());
510   DCHECK(!launcher_);
511   DCHECK(monitoring_notifications_);
512 
513   ReportElapsedTime("attached");
514 
515   // Launch elevated on Win8 to be able to inject Alt+Tab.
516   bool launch_elevated = base::win::GetVersion() >= base::win::VERSION_WIN8;
517 
518   // Get the name of the executable to run. |kDesktopBinaryName| specifies
519   // uiAccess="true" in it's manifest.
520   base::FilePath desktop_binary;
521   bool result;
522   if (launch_elevated) {
523     result = GetInstalledBinaryPath(kDesktopBinaryName, &desktop_binary);
524   } else {
525     result = GetInstalledBinaryPath(kHostBinaryName, &desktop_binary);
526   }
527 
528   if (!result) {
529     TerminateSession();
530     return;
531   }
532 
533   session_attach_timer_.Stop();
534 
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(),
539                            kCopiedSwitchNames,
540                            arraysize(kCopiedSwitchNames));
541 
542   // Create a delegate capable of launching a process in a different session.
543   scoped_ptr<WtsSessionProcessDelegate> delegate(
544       new WtsSessionProcessDelegate(io_task_runner_,
545                                     target.Pass(),
546                                     launch_elevated,
547                                     base::WideToUTF8(
548                                         kDaemonIpcSecurityDescriptor)));
549   if (!delegate->Initialize(session_id)) {
550     TerminateSession();
551     return;
552   }
553 
554   // Create a launcher for the desktop process, using the per-session delegate.
555   launcher_.reset(new WorkerProcessLauncher(delegate.Pass(), this));
556 }
557 
OnSessionDetached()558 void DesktopSessionWin::OnSessionDetached() {
559   DCHECK(caller_task_runner_->BelongsToCurrentThread());
560 
561   launcher_.reset();
562 
563   if (monitoring_notifications_) {
564     ReportElapsedTime("detached");
565 
566     session_attach_timer_.Start(
567         FROM_HERE, base::TimeDelta::FromSeconds(kSessionAttachTimeoutSeconds),
568         this, &DesktopSessionWin::OnSessionAttachTimeout);
569   }
570 }
571 
OnDesktopSessionAgentAttached(IPC::PlatformFileForTransit desktop_pipe)572 void DesktopSessionWin::OnDesktopSessionAgentAttached(
573       IPC::PlatformFileForTransit desktop_pipe) {
574   if (!daemon_process()->OnDesktopSessionAgentAttached(id(),
575                                                        desktop_process_,
576                                                        desktop_pipe)) {
577     CrashDesktopProcess(FROM_HERE);
578   }
579 }
580 
CrashDesktopProcess(const tracked_objects::Location & location)581 void DesktopSessionWin::CrashDesktopProcess(
582     const tracked_objects::Location& location) {
583   DCHECK(caller_task_runner_->BelongsToCurrentThread());
584 
585   launcher_->Crash(location);
586 }
587 
ReportElapsedTime(const std::string & event)588 void DesktopSessionWin::ReportElapsedTime(const std::string& event) {
589   base::Time now = base::Time::Now();
590 
591   std::string passed;
592   if (!last_timestamp_.is_null()) {
593     passed = base::StringPrintf(", %.2fs passed",
594                                 (now - last_timestamp_).InSecondsF());
595   }
596 
597   base::Time::Exploded exploded;
598   now.LocalExplode(&exploded);
599   VLOG(1) << base::StringPrintf("session(%d): %s at %02d:%02d:%02d.%03d%s",
600                                 id(),
601                                 event.c_str(),
602                                 exploded.hour,
603                                 exploded.minute,
604                                 exploded.second,
605                                 exploded.millisecond,
606                                 passed.c_str());
607 
608   last_timestamp_ = now;
609 }
610 
611 }  // namespace remoting
612