Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(19)

Side by Side Diff: remoting/host/plugin/daemon_controller_win.cc

Issue 10161034: Making sure that UAC promts fired by the Chromoting plugin get focus (instead of being shown in the… (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: A fixup Created 8 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "remoting/host/plugin/daemon_controller.h" 5 #include "remoting/host/plugin/daemon_controller.h"
6 6
7 #include <objbase.h> 7 #include <objbase.h>
8 8
9 #include "base/basictypes.h" 9 #include "base/basictypes.h"
10 #include "base/bind.h" 10 #include "base/bind.h"
11 #include "base/bind_helpers.h" 11 #include "base/bind_helpers.h"
12 #include "base/compiler_specific.h" 12 #include "base/compiler_specific.h"
13 #include "base/file_path.h" 13 #include "base/file_path.h"
14 #include "base/file_util.h" 14 #include "base/file_util.h"
15 #include "base/json/json_reader.h" 15 #include "base/json/json_reader.h"
16 #include "base/json/json_writer.h" 16 #include "base/json/json_writer.h"
17 #include "base/logging.h" 17 #include "base/logging.h"
18 #include "base/string16.h" 18 #include "base/string16.h"
19 #include "base/stringize_macros.h" 19 #include "base/stringize_macros.h"
20 #include "base/threading/thread.h" 20 #include "base/threading/thread.h"
21 #include "base/time.h"
22 #include "base/timer.h"
21 #include "base/utf_string_conversions.h" 23 #include "base/utf_string_conversions.h"
22 #include "base/values.h" 24 #include "base/values.h"
23 #include "base/win/scoped_bstr.h" 25 #include "base/win/scoped_bstr.h"
24 #include "base/win/scoped_comptr.h" 26 #include "base/win/scoped_comptr.h"
25 #include "remoting/base/scoped_sc_handle_win.h" 27 #include "remoting/base/scoped_sc_handle_win.h"
26 #include "remoting/host/branding.h" 28 #include "remoting/host/branding.h"
27 #include "remoting/host/daemon_controller_common_win.h" 29 #include "remoting/host/daemon_controller_common_win.h"
28 #include "remoting/host/plugin/daemon_installer_win.h" 30 #include "remoting/host/plugin/daemon_installer_win.h"
29 31
30 // MIDL-generated declarations and definitions. 32 // MIDL-generated declarations and definitions.
31 #include "remoting/host/elevated_controller.h" 33 #include "remoting/host/elevated_controller.h"
32 34
33 using base::win::ScopedBstr; 35 using base::win::ScopedBstr;
34 using base::win::ScopedComPtr; 36 using base::win::ScopedComPtr;
35 37
36 namespace remoting { 38 namespace remoting {
37 39
38 namespace { 40 namespace {
39 41
40 // The COM elevation moniker for the elevated controller. 42 // The COM elevation moniker for the Elevated Controller.
41 const char16 kDaemonControllerElevationMoniker[] = 43 const char16 kDaemonControllerElevationMoniker[] =
42 TO_L_STRING("Elevation:Administrator!new:") 44 TO_L_STRING("Elevation:Administrator!new:")
43 TO_L_STRING("ChromotingElevatedController.ElevatedController"); 45 TO_L_STRING("ChromotingElevatedController.ElevatedController");
44 46
45 // Name of the Daemon Controller's worker thread. 47 // Name of the Daemon Controller's worker thread.
46 const char kDaemonControllerThreadName[] = "Daemon Controller thread"; 48 const char kDaemonControllerThreadName[] = "Daemon Controller thread";
47 49
50 // The maximum interval between showing UAC prompts.
51 const int kUacTimeoutSec = 15 * 60;
Jamie 2012/04/25 21:20:37 Is this strictly needed as part of this CL? It fee
alexeypa (please no reviews) 2012/04/25 21:59:24 I'll move it to a separate CL.
52
48 // A base::Thread implementation that initializes COM on the new thread. 53 // A base::Thread implementation that initializes COM on the new thread.
49 class ComThread : public base::Thread { 54 class ComThread : public base::Thread {
50 public: 55 public:
51 explicit ComThread(const char* name); 56 explicit ComThread(const char* name);
52 57
53 // Activates an elevated instance of the controller and returns the pointer 58 // Activates an elevated instance of the controller and returns the pointer
54 // to the control interface in |control_out|. This class keeps the ownership 59 // to the control interface in |control_out|. This class keeps the ownership
55 // of the pointer so the caller should not call call AddRef() or Release(). 60 // of the pointer so the caller should not call call AddRef() or Release().
56 HRESULT ActivateElevatedController(IDaemonControl** control_out); 61 HRESULT ActivateElevatedController(void* window_handle,
Jamie 2012/04/25 21:20:37 HWND?
alexeypa (please no reviews) 2012/04/25 21:59:24 Done.
62 IDaemonControl** control_out);
57 63
58 bool Start(); 64 bool Start();
59 65
60 protected: 66 protected:
61 virtual void Init() OVERRIDE; 67 virtual void Init() OVERRIDE;
62 virtual void CleanUp() OVERRIDE; 68 virtual void CleanUp() OVERRIDE;
63 69
70 void ReleaseElevatedController();
71
64 ScopedComPtr<IDaemonControl> control_; 72 ScopedComPtr<IDaemonControl> control_;
65 73
74 // This timer is used to release |control_| after a timeout.
75 base::OneShotTimer<ComThread> release_timer_;
76
66 DISALLOW_COPY_AND_ASSIGN(ComThread); 77 DISALLOW_COPY_AND_ASSIGN(ComThread);
67 }; 78 };
68 79
69 class DaemonControllerWin : public remoting::DaemonController { 80 class DaemonControllerWin : public remoting::DaemonController {
70 public: 81 public:
71 DaemonControllerWin(); 82 DaemonControllerWin();
72 virtual ~DaemonControllerWin(); 83 virtual ~DaemonControllerWin();
73 84
74 virtual State GetState() OVERRIDE; 85 virtual State GetState() OVERRIDE;
75 virtual void GetConfig(const GetConfigCallback& callback) OVERRIDE; 86 virtual void GetConfig(const GetConfigCallback& callback) OVERRIDE;
76 virtual void SetConfigAndStart( 87 virtual void SetConfigAndStart(
77 scoped_ptr<base::DictionaryValue> config, 88 scoped_ptr<base::DictionaryValue> config,
78 const CompletionCallback& done_callback) OVERRIDE; 89 const CompletionCallback& done_callback) OVERRIDE;
79 virtual void UpdateConfig(scoped_ptr<base::DictionaryValue> config, 90 virtual void UpdateConfig(scoped_ptr<base::DictionaryValue> config,
80 const CompletionCallback& done_callback) OVERRIDE; 91 const CompletionCallback& done_callback) OVERRIDE;
81 virtual void Stop(const CompletionCallback& done_callback) OVERRIDE; 92 virtual void Stop(const CompletionCallback& done_callback) OVERRIDE;
93 virtual void SetWindow(void* window_handle) OVERRIDE;
82 94
83 private: 95 private:
84 // Converts a Windows service status code to a Daemon state. 96 // Converts a Windows service status code to a Daemon state.
85 static State ConvertToDaemonState(DWORD service_state); 97 static State ConvertToDaemonState(DWORD service_state);
86 98
87 // Converts HRESULT to the AsyncResult. 99 // Converts HRESULT to the AsyncResult.
88 static AsyncResult HResultToAsyncResult(HRESULT hr); 100 static AsyncResult HResultToAsyncResult(HRESULT hr);
89 101
90 // Procedes with the daemon configuration if the installation succeeded, 102 // Procedes with the daemon configuration if the installation succeeded,
91 // otherwise reports the error. 103 // otherwise reports the error.
92 void OnInstallationComplete(scoped_ptr<base::DictionaryValue> config, 104 void OnInstallationComplete(scoped_ptr<base::DictionaryValue> config,
93 const CompletionCallback& done_callback, 105 const CompletionCallback& done_callback,
94 HRESULT result); 106 HRESULT result);
95 107
96 // Opens the Chromoting service returning its handle in |service_out|. 108 // Opens the Chromoting service returning its handle in |service_out|.
97 DWORD OpenService(ScopedScHandle* service_out); 109 DWORD OpenService(ScopedScHandle* service_out);
98 110
99 // The functions that actually do the work. They should be called in 111 // The functions that actually do the work. They should be called in
100 // the context of |worker_thread_|; 112 // the context of |worker_thread_|;
101 void DoGetConfig(const GetConfigCallback& callback); 113 void DoGetConfig(const GetConfigCallback& callback);
102 void DoInstallAsNeededAndStart(scoped_ptr<base::DictionaryValue> config, 114 void DoInstallAsNeededAndStart(scoped_ptr<base::DictionaryValue> config,
103 const CompletionCallback& done_callback); 115 const CompletionCallback& done_callback);
104 void DoSetConfigAndStart(scoped_ptr<base::DictionaryValue> config, 116 void DoSetConfigAndStart(scoped_ptr<base::DictionaryValue> config,
105 const CompletionCallback& done_callback); 117 const CompletionCallback& done_callback);
106 void DoUpdateConfig(scoped_ptr<base::DictionaryValue> config, 118 void DoUpdateConfig(scoped_ptr<base::DictionaryValue> config,
107 const CompletionCallback& done_callback); 119 const CompletionCallback& done_callback);
108 void DoStop(const CompletionCallback& done_callback); 120 void DoStop(const CompletionCallback& done_callback);
121 void DoSetWindow(void* window_handle);
122
123 // Handle of the plugin window.
124 void* window_handle_;
Jamie 2012/04/25 21:20:37 Store as an HWND?
alexeypa (please no reviews) 2012/04/25 21:59:24 Done.
109 125
110 // The worker thread used for servicing long running operations. 126 // The worker thread used for servicing long running operations.
111 ComThread worker_thread_; 127 ComThread worker_thread_;
112 128
113 scoped_ptr<DaemonInstallerWin> installer_; 129 scoped_ptr<DaemonInstallerWin> installer_;
114 130
115 DISALLOW_COPY_AND_ASSIGN(DaemonControllerWin); 131 DISALLOW_COPY_AND_ASSIGN(DaemonControllerWin);
116 }; 132 };
117 133
118 ComThread::ComThread(const char* name) : base::Thread(name), control_(NULL) { 134 ComThread::ComThread(const char* name) : base::Thread(name), control_(NULL) {
119 } 135 }
120 136
121 void ComThread::Init() {
122 CoInitialize(NULL);
123 }
124
125 void ComThread::CleanUp() {
126 control_.Release();
127 CoUninitialize();
128 }
129
130 HRESULT ComThread::ActivateElevatedController( 137 HRESULT ComThread::ActivateElevatedController(
138 void* window_handle,
131 IDaemonControl** control_out) { 139 IDaemonControl** control_out) {
132 // Chache the instance of Elevated Controller to prevent a UAC prompt on every 140 // Cache an instance of the Elevated Controller to prevent a UAC prompt on
133 // operation. 141 // every operation.
134 if (control_.get() == NULL) { 142 if (control_.get() == NULL) {
135 BIND_OPTS3 bind_options; 143 BIND_OPTS3 bind_options;
136 memset(&bind_options, 0, sizeof(bind_options)); 144 memset(&bind_options, 0, sizeof(bind_options));
137 bind_options.cbStruct = sizeof(bind_options); 145 bind_options.cbStruct = sizeof(bind_options);
138 bind_options.hwnd = NULL; 146 bind_options.hwnd =
147 GetTopLevelWindow(reinterpret_cast<HWND>(window_handle));
139 bind_options.dwClassContext = CLSCTX_LOCAL_SERVER; 148 bind_options.dwClassContext = CLSCTX_LOCAL_SERVER;
140 149
141 HRESULT hr = ::CoGetObject( 150 HRESULT hr = ::CoGetObject(
142 kDaemonControllerElevationMoniker, 151 kDaemonControllerElevationMoniker,
143 &bind_options, 152 &bind_options,
144 IID_IDaemonControl, 153 IID_IDaemonControl,
145 control_.ReceiveVoid()); 154 control_.ReceiveVoid());
146 if (FAILED(hr)) { 155 if (FAILED(hr)) {
147 return hr; 156 return hr;
148 } 157 }
158
159 // Release |control_| upon expiration of the timeout.
160 release_timer_.Start(FROM_HERE,
161 base::TimeDelta::FromSeconds(kUacTimeoutSec),
162 this, &ComThread::ReleaseElevatedController);
149 } 163 }
150 164
151 *control_out = control_.get(); 165 *control_out = control_.get();
152 return S_OK; 166 return S_OK;
153 } 167 }
154 168
155 bool ComThread::Start() { 169 bool ComThread::Start() {
156 // N.B. The single threaded COM apartment must be run on a UI message loop. 170 // N.B. The single threaded COM apartment must be run on a UI message loop.
157 base::Thread::Options thread_options(MessageLoop::TYPE_UI, 0); 171 base::Thread::Options thread_options(MessageLoop::TYPE_UI, 0);
158 return StartWithOptions(thread_options); 172 return StartWithOptions(thread_options);
159 } 173 }
160 174
175 void ComThread::Init() {
176 CoInitialize(NULL);
177 }
178
179 void ComThread::CleanUp() {
180 ReleaseElevatedController();
181 CoUninitialize();
182 }
183
184 void ComThread::ReleaseElevatedController() {
185 control_.Release();
186 }
187
Jamie 2012/04/25 21:20:37 It's not clear to me which of the previous functio
alexeypa (please no reviews) 2012/04/25 21:59:24 I'll move the timeout change to a separate CL.
161 DaemonControllerWin::DaemonControllerWin() 188 DaemonControllerWin::DaemonControllerWin()
162 : worker_thread_(kDaemonControllerThreadName) { 189 : window_handle_(NULL),
190 worker_thread_(kDaemonControllerThreadName) {
163 if (!worker_thread_.Start()) { 191 if (!worker_thread_.Start()) {
164 LOG(FATAL) << "Failed to start the Daemon Controller worker thread."; 192 LOG(FATAL) << "Failed to start the Daemon Controller worker thread.";
165 } 193 }
166 } 194 }
167 195
168 DaemonControllerWin::~DaemonControllerWin() { 196 DaemonControllerWin::~DaemonControllerWin() {
169 worker_thread_.Stop(); 197 worker_thread_.Stop();
170 } 198 }
171 199
172 remoting::DaemonController::State DaemonControllerWin::GetState() { 200 remoting::DaemonController::State DaemonControllerWin::GetState() {
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after
220 base::Unretained(this), base::Passed(&config), done_callback)); 248 base::Unretained(this), base::Passed(&config), done_callback));
221 } 249 }
222 250
223 void DaemonControllerWin::Stop(const CompletionCallback& done_callback) { 251 void DaemonControllerWin::Stop(const CompletionCallback& done_callback) {
224 worker_thread_.message_loop_proxy()->PostTask( 252 worker_thread_.message_loop_proxy()->PostTask(
225 FROM_HERE, base::Bind( 253 FROM_HERE, base::Bind(
226 &DaemonControllerWin::DoStop, base::Unretained(this), 254 &DaemonControllerWin::DoStop, base::Unretained(this),
227 done_callback)); 255 done_callback));
228 } 256 }
229 257
258 void DaemonControllerWin::SetWindow(void* window_handle) {
259 worker_thread_.message_loop_proxy()->PostTask(
260 FROM_HERE, base::Bind(
261 &DaemonControllerWin::DoSetWindow, base::Unretained(this),
262 window_handle));
263 }
264
230 // static 265 // static
231 remoting::DaemonController::State DaemonControllerWin::ConvertToDaemonState( 266 remoting::DaemonController::State DaemonControllerWin::ConvertToDaemonState(
232 DWORD service_state) { 267 DWORD service_state) {
233 switch (service_state) { 268 switch (service_state) {
234 case SERVICE_RUNNING: 269 case SERVICE_RUNNING:
235 return STATE_STARTED; 270 return STATE_STARTED;
236 271
237 case SERVICE_CONTINUE_PENDING: 272 case SERVICE_CONTINUE_PENDING:
238 case SERVICE_START_PENDING: 273 case SERVICE_START_PENDING:
239 return STATE_STARTING; 274 return STATE_STARTING;
(...skipping 120 matching lines...) Expand 10 before | Expand all | Expand 10 after
360 395
361 callback.Run(scoped_ptr<base::DictionaryValue>(dictionary)); 396 callback.Run(scoped_ptr<base::DictionaryValue>(dictionary));
362 } 397 }
363 398
364 void DaemonControllerWin::DoInstallAsNeededAndStart( 399 void DaemonControllerWin::DoInstallAsNeededAndStart(
365 scoped_ptr<base::DictionaryValue> config, 400 scoped_ptr<base::DictionaryValue> config,
366 const CompletionCallback& done_callback) { 401 const CompletionCallback& done_callback) {
367 DCHECK(worker_thread_.message_loop_proxy()->BelongsToCurrentThread()); 402 DCHECK(worker_thread_.message_loop_proxy()->BelongsToCurrentThread());
368 403
369 IDaemonControl* control = NULL; 404 IDaemonControl* control = NULL;
370 HRESULT hr = worker_thread_.ActivateElevatedController(&control); 405 HRESULT hr = worker_thread_.ActivateElevatedController(window_handle_,
406 &control);
371 407
372 // Just configure and start the Daemon Controller if it is installed already. 408 // Just configure and start the Daemon Controller if it is installed already.
373 if (SUCCEEDED(hr)) { 409 if (SUCCEEDED(hr)) {
374 DoSetConfigAndStart(config.Pass(), done_callback); 410 DoSetConfigAndStart(config.Pass(), done_callback);
375 return; 411 return;
376 } 412 }
377 413
378 // Otherwise, install it if its COM registration entry is missing. 414 // Otherwise, install it if its COM registration entry is missing.
379 if (hr == CO_E_CLASSSTRING) { 415 if (hr == CO_E_CLASSSTRING) {
380 scoped_ptr<DaemonInstallerWin> installer = DaemonInstallerWin::Create( 416 scoped_ptr<DaemonInstallerWin> installer = DaemonInstallerWin::Create(
417 window_handle_,
381 base::Bind(&DaemonControllerWin::OnInstallationComplete, 418 base::Bind(&DaemonControllerWin::OnInstallationComplete,
382 base::Unretained(this), 419 base::Unretained(this),
383 base::Passed(&config), 420 base::Passed(&config),
384 done_callback)); 421 done_callback));
385 if (installer.get()) { 422 if (installer.get()) {
386 DCHECK(!installer_.get()); 423 DCHECK(!installer_.get());
387 installer_ = installer.Pass(); 424 installer_ = installer.Pass();
388 installer_->Install(); 425 installer_->Install();
389 } 426 }
390 } else { 427 } else {
391 LOG(ERROR) << "Failed to initiate the Chromoting Host installation " 428 LOG(ERROR) << "Failed to initiate the Chromoting Host installation "
392 << "(error: 0x" << std::hex << hr << std::dec << ")."; 429 << "(error: 0x" << std::hex << hr << std::dec << ").";
393 done_callback.Run(HResultToAsyncResult(hr)); 430 done_callback.Run(HResultToAsyncResult(hr));
394 } 431 }
395 } 432 }
396 433
397 void DaemonControllerWin::DoSetConfigAndStart( 434 void DaemonControllerWin::DoSetConfigAndStart(
398 scoped_ptr<base::DictionaryValue> config, 435 scoped_ptr<base::DictionaryValue> config,
399 const CompletionCallback& done_callback) { 436 const CompletionCallback& done_callback) {
400 DCHECK(worker_thread_.message_loop_proxy()->BelongsToCurrentThread()); 437 DCHECK(worker_thread_.message_loop_proxy()->BelongsToCurrentThread());
401 438
402 IDaemonControl* control = NULL; 439 IDaemonControl* control = NULL;
403 HRESULT hr = worker_thread_.ActivateElevatedController(&control); 440 HRESULT hr = worker_thread_.ActivateElevatedController(window_handle_,
441 &control);
404 if (FAILED(hr)) { 442 if (FAILED(hr)) {
405 done_callback.Run(HResultToAsyncResult(hr)); 443 done_callback.Run(HResultToAsyncResult(hr));
406 return; 444 return;
407 } 445 }
408 446
409 // Store the configuration. 447 // Store the configuration.
410 std::string file_content; 448 std::string file_content;
411 base::JSONWriter::Write(config.get(), &file_content); 449 base::JSONWriter::Write(config.get(), &file_content);
412 450
413 ScopedBstr host_config(UTF8ToUTF16(file_content).c_str()); 451 ScopedBstr host_config(UTF8ToUTF16(file_content).c_str());
(...skipping 14 matching lines...) Expand all
428 } 466 }
429 467
430 void DaemonControllerWin::DoUpdateConfig( 468 void DaemonControllerWin::DoUpdateConfig(
431 scoped_ptr<base::DictionaryValue> config, 469 scoped_ptr<base::DictionaryValue> config,
432 const CompletionCallback& done_callback) { 470 const CompletionCallback& done_callback) {
433 // TODO(simonmorris): Much of this code was copied from DoSetConfigAndStart(). 471 // TODO(simonmorris): Much of this code was copied from DoSetConfigAndStart().
434 // Refactor. 472 // Refactor.
435 DCHECK(worker_thread_.message_loop_proxy()->BelongsToCurrentThread()); 473 DCHECK(worker_thread_.message_loop_proxy()->BelongsToCurrentThread());
436 474
437 IDaemonControl* control = NULL; 475 IDaemonControl* control = NULL;
438 HRESULT hr = worker_thread_.ActivateElevatedController(&control); 476 HRESULT hr = worker_thread_.ActivateElevatedController(window_handle_,
477 &control);
439 if (FAILED(hr)) { 478 if (FAILED(hr)) {
440 done_callback.Run(HResultToAsyncResult(hr)); 479 done_callback.Run(HResultToAsyncResult(hr));
441 return; 480 return;
442 } 481 }
443 482
444 // Store the configuration. 483 // Store the configuration.
445 std::string file_content; 484 std::string file_content;
446 base::JSONWriter::Write(config.get(), &file_content); 485 base::JSONWriter::Write(config.get(), &file_content);
447 486
448 ScopedBstr host_config(UTF8ToUTF16(file_content).c_str()); 487 ScopedBstr host_config(UTF8ToUTF16(file_content).c_str());
449 if (host_config == NULL) { 488 if (host_config == NULL) {
450 done_callback.Run(HResultToAsyncResult(E_OUTOFMEMORY)); 489 done_callback.Run(HResultToAsyncResult(E_OUTOFMEMORY));
451 return; 490 return;
452 } 491 }
453 492
454 hr = control->UpdateConfig(host_config); 493 hr = control->UpdateConfig(host_config);
455 done_callback.Run(HResultToAsyncResult(hr)); 494 done_callback.Run(HResultToAsyncResult(hr));
456 } 495 }
457 496
458 void DaemonControllerWin::DoStop(const CompletionCallback& done_callback) { 497 void DaemonControllerWin::DoStop(const CompletionCallback& done_callback) {
459 DCHECK(worker_thread_.message_loop_proxy()->BelongsToCurrentThread()); 498 DCHECK(worker_thread_.message_loop_proxy()->BelongsToCurrentThread());
460 499
461 IDaemonControl* control = NULL; 500 IDaemonControl* control = NULL;
462 HRESULT hr = worker_thread_.ActivateElevatedController(&control); 501 HRESULT hr = worker_thread_.ActivateElevatedController(window_handle_,
502 &control);
463 if (FAILED(hr)) { 503 if (FAILED(hr)) {
464 done_callback.Run(HResultToAsyncResult(hr)); 504 done_callback.Run(HResultToAsyncResult(hr));
465 return; 505 return;
466 } 506 }
467 507
468 hr = control->StopDaemon(); 508 hr = control->StopDaemon();
469 done_callback.Run(HResultToAsyncResult(hr)); 509 done_callback.Run(HResultToAsyncResult(hr));
470 } 510 }
471 511
512 void DaemonControllerWin::DoSetWindow(void* window_handle) {
513 window_handle_ = window_handle;
514 }
515
472 } // namespace 516 } // namespace
473 517
474 scoped_ptr<DaemonController> remoting::DaemonController::Create() { 518 scoped_ptr<DaemonController> remoting::DaemonController::Create() {
475 return scoped_ptr<DaemonController>(new DaemonControllerWin()); 519 return scoped_ptr<DaemonController>(new DaemonControllerWin());
476 } 520 }
477 521
478 } // namespace remoting 522 } // namespace remoting
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698