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

Side by Side Diff: remoting/host/setup/daemon_controller_delegate_mac.mm

Issue 23606019: Refactor the daemon controller so that the callbacks are called on the caller thread. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: fix the license Created 7 years, 3 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/setup/daemon_controller.h" 5 #include <CoreFoundation/CoreFoundation.h>
6
7 #include "remoting/host/setup/daemon_controller_delegate_mac.h"
6 8
7 #include <launch.h> 9 #include <launch.h>
8 #include <stdio.h> 10 #include <stdio.h>
9 #include <sys/types.h> 11 #include <sys/types.h>
10 12
11 #include "base/basictypes.h" 13 #include "base/basictypes.h"
12 #include "base/bind.h" 14 #include "base/bind.h"
13 #include "base/compiler_specific.h" 15 #include "base/compiler_specific.h"
14 #include "base/file_util.h" 16 #include "base/file_util.h"
15 #include "base/files/file_path.h" 17 #include "base/files/file_path.h"
16 #include "base/json/json_writer.h" 18 #include "base/json/json_writer.h"
17 #include "base/logging.h" 19 #include "base/logging.h"
18 #include "base/mac/foundation_util.h" 20 #include "base/mac/foundation_util.h"
19 #include "base/mac/launchd.h" 21 #include "base/mac/launchd.h"
20 #include "base/mac/mac_logging.h" 22 #include "base/mac/mac_logging.h"
21 #include "base/mac/mac_util.h" 23 #include "base/mac/mac_util.h"
22 #include "base/mac/scoped_launch_data.h" 24 #include "base/mac/scoped_launch_data.h"
23 #include "base/threading/thread.h"
24 #include "base/time/time.h" 25 #include "base/time/time.h"
25 #include "base/values.h" 26 #include "base/values.h"
26 #include "remoting/host/constants_mac.h" 27 #include "remoting/host/constants_mac.h"
27 #include "remoting/host/json_host_config.h" 28 #include "remoting/host/json_host_config.h"
28 #include "remoting/host/usage_stats_consent.h" 29 #include "remoting/host/usage_stats_consent.h"
29 30
30 namespace remoting { 31 namespace remoting {
31 32
32 namespace { 33 DaemonControllerDelegateMac::DaemonControllerDelegateMac() {
33
34 class DaemonControllerMac : public remoting::DaemonController {
35 public:
36 DaemonControllerMac();
37 virtual ~DaemonControllerMac();
38
39 virtual State GetState() OVERRIDE;
40 virtual void GetConfig(const GetConfigCallback& callback) OVERRIDE;
41 virtual void SetConfigAndStart(
42 scoped_ptr<base::DictionaryValue> config,
43 bool consent,
44 const CompletionCallback& done) OVERRIDE;
45 virtual void UpdateConfig(scoped_ptr<base::DictionaryValue> config,
46 const CompletionCallback& done_callback) OVERRIDE;
47 virtual void Stop(const CompletionCallback& done_callback) OVERRIDE;
48 virtual void SetWindow(void* window_handle) OVERRIDE;
49 virtual void GetVersion(const GetVersionCallback& done_callback) OVERRIDE;
50 virtual void GetUsageStatsConsent(
51 const GetUsageStatsConsentCallback& callback) OVERRIDE;
52
53 private:
54 void DoGetConfig(const GetConfigCallback& callback);
55 void DoGetVersion(const GetVersionCallback& callback);
56 void DoSetConfigAndStart(scoped_ptr<base::DictionaryValue> config,
57 bool consent,
58 const CompletionCallback& done);
59 void DoUpdateConfig(scoped_ptr<base::DictionaryValue> config,
60 const CompletionCallback& done_callback);
61 void DoStop(const CompletionCallback& done_callback);
62 void DoGetUsageStatsConsent(const GetUsageStatsConsentCallback& callback);
63
64 void ShowPreferencePane(const std::string& config_data,
65 const CompletionCallback& done_callback);
66 void RegisterForPreferencePaneNotifications(
67 const CompletionCallback &done_callback);
68 void DeregisterForPreferencePaneNotifications();
69 void PreferencePaneCallbackDelegate(CFStringRef name);
70 static bool DoShowPreferencePane(const std::string& config_data);
71 static void PreferencePaneCallback(CFNotificationCenterRef center,
72 void* observer,
73 CFStringRef name,
74 const void* object,
75 CFDictionaryRef user_info);
76
77 base::Thread auth_thread_;
78 CompletionCallback current_callback_;
79
80 DISALLOW_COPY_AND_ASSIGN(DaemonControllerMac);
81 };
82
83 DaemonControllerMac::DaemonControllerMac()
84 : auth_thread_("Auth thread") {
85 auth_thread_.Start();
86 } 34 }
87 35
88 DaemonControllerMac::~DaemonControllerMac() { 36 DaemonControllerDelegateMac::~DaemonControllerDelegateMac() {
89 auth_thread_.Stop();
90 DeregisterForPreferencePaneNotifications(); 37 DeregisterForPreferencePaneNotifications();
91 } 38 }
92 39
93 void DaemonControllerMac::DeregisterForPreferencePaneNotifications() { 40 DaemonController::State DaemonControllerDelegateMac::GetState() {
94 CFNotificationCenterRemoveObserver(
95 CFNotificationCenterGetDistributedCenter(),
96 this,
97 CFSTR(UPDATE_SUCCEEDED_NOTIFICATION_NAME),
98 NULL);
99 CFNotificationCenterRemoveObserver(
100 CFNotificationCenterGetDistributedCenter(),
101 this,
102 CFSTR(UPDATE_FAILED_NOTIFICATION_NAME),
103 NULL);
104 }
105
106 DaemonController::State DaemonControllerMac::GetState() {
107 pid_t job_pid = base::mac::PIDForJob(kServiceName); 41 pid_t job_pid = base::mac::PIDForJob(kServiceName);
108 if (job_pid < 0) { 42 if (job_pid < 0) {
109 return DaemonController::STATE_NOT_INSTALLED; 43 return DaemonController::STATE_NOT_INSTALLED;
110 } else if (job_pid == 0) { 44 } else if (job_pid == 0) {
111 // Service is stopped, or a start attempt failed. 45 // Service is stopped, or a start attempt failed.
112 return DaemonController::STATE_STOPPED; 46 return DaemonController::STATE_STOPPED;
113 } else { 47 } else {
114 return DaemonController::STATE_STARTED; 48 return DaemonController::STATE_STARTED;
115 } 49 }
116 } 50 }
117 51
118 void DaemonControllerMac::GetConfig(const GetConfigCallback& callback) { 52 scoped_ptr<base::DictionaryValue> DaemonControllerDelegateMac::GetConfig() {
119 // base::Unretained() is safe, since this object owns the thread and therefore
120 // outlives it.
121 auth_thread_.message_loop_proxy()->PostTask(
122 FROM_HERE,
123 base::Bind(&DaemonControllerMac::DoGetConfig, base::Unretained(this),
124 callback));
125 }
126
127 void DaemonControllerMac::SetConfigAndStart(
128 scoped_ptr<base::DictionaryValue> config,
129 bool consent,
130 const CompletionCallback& done) {
131 auth_thread_.message_loop_proxy()->PostTask(
132 FROM_HERE, base::Bind(
133 &DaemonControllerMac::DoSetConfigAndStart, base::Unretained(this),
134 base::Passed(&config), consent, done));
135 }
136
137 void DaemonControllerMac::UpdateConfig(
138 scoped_ptr<base::DictionaryValue> config,
139 const CompletionCallback& done_callback) {
140 auth_thread_.message_loop()->PostTask(FROM_HERE, base::Bind(
141 &DaemonControllerMac::DoUpdateConfig, base::Unretained(this),
142 base::Passed(&config), done_callback));
143 }
144
145 void DaemonControllerMac::Stop(const CompletionCallback& done_callback) {
146 auth_thread_.message_loop_proxy()->PostTask(
147 FROM_HERE, base::Bind(
148 &DaemonControllerMac::DoStop, base::Unretained(this), done_callback));
149 }
150
151 void DaemonControllerMac::SetWindow(void* window_handle) {
152 // noop
153 }
154
155 void DaemonControllerMac::GetVersion(const GetVersionCallback& callback) {
156 auth_thread_.message_loop_proxy()->PostTask(
157 FROM_HERE,
158 base::Bind(&DaemonControllerMac::DoGetVersion, base::Unretained(this),
159 callback));
160 }
161
162 void DaemonControllerMac::GetUsageStatsConsent(
163 const GetUsageStatsConsentCallback& callback) {
164 auth_thread_.message_loop_proxy()->PostTask(
165 FROM_HERE,
166 base::Bind(&DaemonControllerMac::DoGetUsageStatsConsent,
167 base::Unretained(this), callback));
168 }
169
170 void DaemonControllerMac::DoGetConfig(const GetConfigCallback& callback) {
171 base::FilePath config_path(kHostConfigFilePath); 53 base::FilePath config_path(kHostConfigFilePath);
172 JsonHostConfig host_config(config_path); 54 JsonHostConfig host_config(config_path);
173 scoped_ptr<base::DictionaryValue> config; 55 scoped_ptr<base::DictionaryValue> config;
174 56
175 if (host_config.Read()) { 57 if (host_config.Read()) {
176 config.reset(new base::DictionaryValue()); 58 config.reset(new base::DictionaryValue());
177 std::string value; 59 std::string value;
178 if (host_config.GetString(kHostIdConfigPath, &value)) 60 if (host_config.GetString(kHostIdConfigPath, &value))
179 config.get()->SetString(kHostIdConfigPath, value); 61 config.get()->SetString(kHostIdConfigPath, value);
180 if (host_config.GetString(kXmppLoginConfigPath, &value)) 62 if (host_config.GetString(kXmppLoginConfigPath, &value))
181 config.get()->SetString(kXmppLoginConfigPath, value); 63 config.get()->SetString(kXmppLoginConfigPath, value);
182 } 64 }
183 65
184 callback.Run(config.Pass()); 66 return config.Pass();
185 } 67 }
186 68
187 void DaemonControllerMac::DoGetVersion(const GetVersionCallback& callback) { 69 void DaemonControllerDelegateMac::SetConfigAndStart(
70 scoped_ptr<base::DictionaryValue> config,
71 bool consent,
72 const DaemonController::CompletionCallback& done) {
73 config->SetBoolean(kUsageStatsConsentConfigPath, consent);
74 std::string config_data;
75 base::JSONWriter::Write(config.get(), &config_data);
76 ShowPreferencePane(config_data, done);
77 }
78
79 void DaemonControllerDelegateMac::UpdateConfig(
80 scoped_ptr<base::DictionaryValue> config,
81 const DaemonController::CompletionCallback& done) {
82 base::FilePath config_file_path(kHostConfigFilePath);
83 JsonHostConfig config_file(config_file_path);
84 if (!config_file.Read()) {
85 done.Run(DaemonController::RESULT_FAILED);
86 return;
87 }
88 if (!config_file.CopyFrom(config.get())) {
89 LOG(ERROR) << "Failed to update configuration.";
90 done.Run(DaemonController::RESULT_FAILED);
91 return;
92 }
93
94 std::string config_data = config_file.GetSerializedData();
95 ShowPreferencePane(config_data, done);
96 }
97
98 void DaemonControllerDelegateMac::Stop(
99 const DaemonController::CompletionCallback& done) {
100 ShowPreferencePane("", done);
101 }
102
103 void DaemonControllerDelegateMac::SetWindow(void* window_handle) {
104 // noop
105 }
106
107 std::string DaemonControllerDelegateMac::GetVersion() {
188 std::string version = ""; 108 std::string version = "";
189 std::string command_line = remoting::kHostHelperScriptPath; 109 std::string command_line = remoting::kHostHelperScriptPath;
190 command_line += " --host-version"; 110 command_line += " --host-version";
191 FILE* script_output = popen(command_line.c_str(), "r"); 111 FILE* script_output = popen(command_line.c_str(), "r");
192 if (script_output) { 112 if (script_output) {
193 char buffer[100]; 113 char buffer[100];
194 char* result = fgets(buffer, sizeof(buffer), script_output); 114 char* result = fgets(buffer, sizeof(buffer), script_output);
195 pclose(script_output); 115 pclose(script_output);
196 if (result) { 116 if (result) {
197 // The string is guaranteed to be null-terminated, but probably contains 117 // The string is guaranteed to be null-terminated, but probably contains
198 // a newline character, which we don't want. 118 // a newline character, which we don't want.
199 for (int i = 0; result[i]; ++i) { 119 for (int i = 0; result[i]; ++i) {
200 if (result[i] < ' ') { 120 if (result[i] < ' ') {
201 result[i] = 0; 121 result[i] = 0;
202 break; 122 break;
203 } 123 }
204 } 124 }
205 version = result; 125 version = result;
206 } 126 }
207 } 127 }
208 callback.Run(version); 128
129 return version;
209 } 130 }
210 131
211 void DaemonControllerMac::DoSetConfigAndStart( 132 DaemonController::UsageStatsConsent
212 scoped_ptr<base::DictionaryValue> config, 133 DaemonControllerDelegateMac::GetUsageStatsConsent() {
213 bool consent, 134 DaemonController::UsageStatsConsent consent;
214 const CompletionCallback& done) { 135 consent.supported = true;
215 config->SetBoolean(kUsageStatsConsentConfigPath, consent); 136 consent.allowed = false;
216 std::string config_data; 137 // set_by_policy is not yet supported.
217 base::JSONWriter::Write(config.get(), &config_data); 138 consent.set_by_policy = false;
218 ShowPreferencePane(config_data, done); 139
140 base::FilePath config_file_path(kHostConfigFilePath);
141 JsonHostConfig host_config(config_file_path);
142 if (host_config.Read()) {
143 host_config.GetBoolean(kUsageStatsConsentConfigPath, &consent.allowed);
144 }
145
146 return consent;
219 } 147 }
220 148
221 void DaemonControllerMac::DoUpdateConfig( 149 void DaemonControllerDelegateMac::ShowPreferencePane(
222 scoped_ptr<base::DictionaryValue> config, 150 const std::string& config_data,
223 const CompletionCallback& done_callback) { 151 const DaemonController::CompletionCallback& done) {
224 base::FilePath config_file_path(kHostConfigFilePath); 152 if (DoShowPreferencePane(config_data)) {
225 JsonHostConfig config_file(config_file_path); 153 RegisterForPreferencePaneNotifications(done);
226 if (!config_file.Read()) { 154 } else {
227 done_callback.Run(RESULT_FAILED); 155 done.Run(DaemonController::RESULT_FAILED);
228 return;
229 } 156 }
230 if (!config_file.CopyFrom(config.get())) { 157 }
231 LOG(ERROR) << "Failed to update configuration."; 158
232 done_callback.Run(RESULT_FAILED); 159 // CFNotificationCenterAddObserver ties the thread on which distributed
160 // notifications are received to the one on which it is first called.
161 // This is safe because HostNPScriptObject::InvokeAsyncResultCallback
162 // bounces the invocation to the correct thread, so it doesn't matter
163 // which thread CompletionCallbacks are called on.
164 void DaemonControllerDelegateMac::RegisterForPreferencePaneNotifications(
165 const DaemonController::CompletionCallback& done) {
166 // We can only have one callback registered at a time. This is enforced by the
167 // UX flow of the web-app.
168 DCHECK(current_callback_.is_null());
169 current_callback_ = done;
170
171 CFNotificationCenterAddObserver(
172 CFNotificationCenterGetDistributedCenter(),
173 this,
174 &DaemonControllerDelegateMac::PreferencePaneCallback,
175 CFSTR(UPDATE_SUCCEEDED_NOTIFICATION_NAME),
176 NULL,
177 CFNotificationSuspensionBehaviorDeliverImmediately);
178 CFNotificationCenterAddObserver(
179 CFNotificationCenterGetDistributedCenter(),
180 this,
181 &DaemonControllerDelegateMac::PreferencePaneCallback,
182 CFSTR(UPDATE_FAILED_NOTIFICATION_NAME),
183 NULL,
184 CFNotificationSuspensionBehaviorDeliverImmediately);
185 }
186
187 void DaemonControllerDelegateMac::DeregisterForPreferencePaneNotifications() {
188 CFNotificationCenterRemoveObserver(
189 CFNotificationCenterGetDistributedCenter(),
190 this,
191 CFSTR(UPDATE_SUCCEEDED_NOTIFICATION_NAME),
192 NULL);
193 CFNotificationCenterRemoveObserver(
194 CFNotificationCenterGetDistributedCenter(),
195 this,
196 CFSTR(UPDATE_FAILED_NOTIFICATION_NAME),
197 NULL);
198 }
199
200 void DaemonControllerDelegateMac::PreferencePaneCallbackDelegate(
201 CFStringRef name) {
202 DaemonController::AsyncResult result = DaemonController::RESULT_FAILED;
203 if (CFStringCompare(name, CFSTR(UPDATE_SUCCEEDED_NOTIFICATION_NAME), 0) ==
204 kCFCompareEqualTo) {
205 result = DaemonController::RESULT_OK;
206 } else if (CFStringCompare(name, CFSTR(UPDATE_FAILED_NOTIFICATION_NAME), 0) ==
207 kCFCompareEqualTo) {
208 result = DaemonController::RESULT_FAILED;
209 } else {
210 LOG(WARNING) << "Ignoring unexpected notification: " << name;
233 return; 211 return;
234 } 212 }
235 213
236 std::string config_data = config_file.GetSerializedData(); 214 DCHECK(!current_callback_.is_null());
237 ShowPreferencePane(config_data, done_callback); 215 DaemonController::CompletionCallback done = current_callback_;
216 current_callback_.Reset();
217 done.Run(result);
218
219 DeregisterForPreferencePaneNotifications();
238 } 220 }
239 221
240 void DaemonControllerMac::DoGetUsageStatsConsent( 222 // static
241 const GetUsageStatsConsentCallback& callback) { 223 bool DaemonControllerDelegateMac::DoShowPreferencePane(
242 bool allowed = false; 224 const std::string& config_data) {
243 base::FilePath config_file_path(kHostConfigFilePath);
244 JsonHostConfig host_config(config_file_path);
245 if (host_config.Read()) {
246 host_config.GetBoolean(kUsageStatsConsentConfigPath, &allowed);
247 }
248 // set_by_policy is not yet supported.
249 callback.Run(true, allowed, false /* set_by_policy */);
250 }
251
252 void DaemonControllerMac::ShowPreferencePane(
253 const std::string& config_data, const CompletionCallback& done_callback) {
254 if (DoShowPreferencePane(config_data)) {
255 RegisterForPreferencePaneNotifications(done_callback);
256 } else {
257 done_callback.Run(RESULT_FAILED);
258 }
259 }
260
261 bool DaemonControllerMac::DoShowPreferencePane(const std::string& config_data) {
262 if (!config_data.empty()) { 225 if (!config_data.empty()) {
263 base::FilePath config_path; 226 base::FilePath config_path;
264 if (!file_util::GetTempDir(&config_path)) { 227 if (!file_util::GetTempDir(&config_path)) {
265 LOG(ERROR) << "Failed to get filename for saving configuration data."; 228 LOG(ERROR) << "Failed to get filename for saving configuration data.";
266 return false; 229 return false;
267 } 230 }
268 config_path = config_path.Append(kHostConfigFileName); 231 config_path = config_path.Append(kHostConfigFileName);
269 232
270 int written = file_util::WriteFile(config_path, config_data.data(), 233 int written = file_util::WriteFile(config_path, config_data.data(),
271 config_data.size()); 234 config_data.size());
(...skipping 27 matching lines...) Expand all
299 262
300 CFNotificationCenterRef center = 263 CFNotificationCenterRef center =
301 CFNotificationCenterGetDistributedCenter(); 264 CFNotificationCenterGetDistributedCenter();
302 base::ScopedCFTypeRef<CFStringRef> service_name(CFStringCreateWithCString( 265 base::ScopedCFTypeRef<CFStringRef> service_name(CFStringCreateWithCString(
303 kCFAllocatorDefault, remoting::kServiceName, kCFStringEncodingUTF8)); 266 kCFAllocatorDefault, remoting::kServiceName, kCFStringEncodingUTF8));
304 CFNotificationCenterPostNotification(center, service_name, NULL, NULL, 267 CFNotificationCenterPostNotification(center, service_name, NULL, NULL,
305 TRUE); 268 TRUE);
306 return true; 269 return true;
307 } 270 }
308 271
309 void DaemonControllerMac::DoStop(const CompletionCallback& done_callback) { 272 // static
310 ShowPreferencePane("", done_callback); 273 void DaemonControllerDelegateMac::PreferencePaneCallback(
274 CFNotificationCenterRef center,
275 void* observer,
276 CFStringRef name,
277 const void* object,
278 CFDictionaryRef user_info) {
279 DaemonControllerDelegateMac* self =
280 reinterpret_cast<DaemonControllerDelegateMac*>(observer);
281 if (!self) {
282 LOG(WARNING) << "Ignoring notification with NULL observer: " << name;
283 return;
284 }
285
286 self->PreferencePaneCallbackDelegate(name);
311 } 287 }
312 288
313 // CFNotificationCenterAddObserver ties the thread on which distributed 289 scoped_refptr<DaemonController> DaemonController::Create() {
314 // notifications are received to the one on which it is first called. 290 scoped_ptr<DaemonController::Delegate> delegate(
315 // This is safe because HostNPScriptObject::InvokeAsyncResultCallback 291 new DaemonControllerDelegateMac());
316 // bounces the invocation to the correct thread, so it doesn't matter 292 return new DaemonController(delegate.Pass());
317 // which thread CompletionCallbacks are called on.
318 void DaemonControllerMac::RegisterForPreferencePaneNotifications(
319 const CompletionCallback& done_callback) {
320 // We can only have one callback registered at a time. This is enforced by the
321 // UX flow of the web-app.
322 DCHECK(current_callback_.is_null());
323 current_callback_ = done_callback;
324
325 CFNotificationCenterAddObserver(
326 CFNotificationCenterGetDistributedCenter(),
327 this,
328 &DaemonControllerMac::PreferencePaneCallback,
329 CFSTR(UPDATE_SUCCEEDED_NOTIFICATION_NAME),
330 NULL,
331 CFNotificationSuspensionBehaviorDeliverImmediately);
332 CFNotificationCenterAddObserver(
333 CFNotificationCenterGetDistributedCenter(),
334 this,
335 &DaemonControllerMac::PreferencePaneCallback,
336 CFSTR(UPDATE_FAILED_NOTIFICATION_NAME),
337 NULL,
338 CFNotificationSuspensionBehaviorDeliverImmediately);
339 }
340
341 void DaemonControllerMac::PreferencePaneCallbackDelegate(CFStringRef name) {
342 AsyncResult result = RESULT_FAILED;
343 if (CFStringCompare(name, CFSTR(UPDATE_SUCCEEDED_NOTIFICATION_NAME), 0) ==
344 kCFCompareEqualTo) {
345 result = RESULT_OK;
346 } else if (CFStringCompare(name, CFSTR(UPDATE_FAILED_NOTIFICATION_NAME), 0) ==
347 kCFCompareEqualTo) {
348 result = RESULT_FAILED;
349 } else {
350 LOG(WARNING) << "Ignoring unexpected notification: " << name;
351 return;
352 }
353 DCHECK(!current_callback_.is_null());
354 current_callback_.Run(result);
355 current_callback_.Reset();
356 DeregisterForPreferencePaneNotifications();
357 }
358
359 void DaemonControllerMac::PreferencePaneCallback(CFNotificationCenterRef center,
360 void* observer,
361 CFStringRef name,
362 const void* object,
363 CFDictionaryRef user_info) {
364 DaemonControllerMac* self = reinterpret_cast<DaemonControllerMac*>(observer);
365 if (self) {
366 self->PreferencePaneCallbackDelegate(name);
367 } else {
368 LOG(WARNING) << "Ignoring notification with NULL observer: " << name;
369 }
370 }
371
372 } // namespace
373
374 scoped_ptr<DaemonController> remoting::DaemonController::Create() {
375 return scoped_ptr<DaemonController>(new DaemonControllerMac());
376 } 293 }
377 294
378 } // namespace remoting 295 } // namespace remoting
OLDNEW
« no previous file with comments | « remoting/host/setup/daemon_controller_delegate_mac.h ('k') | remoting/host/setup/daemon_controller_delegate_win.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698