1 // Copyright 2013 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 <CoreFoundation/CoreFoundation.h>
7 #include "remoting/host/setup/daemon_controller_delegate_mac.h"
11 #include <sys/types.h>
13 #include "base/basictypes.h"
14 #include "base/bind.h"
15 #include "base/callback_helpers.h"
16 #include "base/compiler_specific.h"
17 #include "base/files/file_path.h"
18 #include "base/files/file_util.h"
19 #include "base/logging.h"
20 #include "base/mac/foundation_util.h"
21 #include "base/mac/launchd.h"
22 #include "base/mac/mac_logging.h"
23 #include "base/mac/mac_util.h"
24 #include "base/mac/scoped_launch_data.h"
25 #include "base/time/time.h"
26 #include "base/values.h"
27 #include "remoting/host/constants_mac.h"
28 #include "remoting/host/host_config.h"
29 #include "remoting/host/usage_stats_consent.h"
33 DaemonControllerDelegateMac::DaemonControllerDelegateMac() {
36 DaemonControllerDelegateMac::~DaemonControllerDelegateMac() {
37 DeregisterForPreferencePaneNotifications();
40 DaemonController::State DaemonControllerDelegateMac::GetState() {
41 pid_t job_pid = base::mac::PIDForJob(kServiceName);
43 return DaemonController::STATE_UNKNOWN;
44 } else if (job_pid == 0) {
45 // Service is stopped, or a start attempt failed.
46 return DaemonController::STATE_STOPPED;
48 return DaemonController::STATE_STARTED;
52 scoped_ptr<base::DictionaryValue> DaemonControllerDelegateMac::GetConfig() {
53 base::FilePath config_path(kHostConfigFilePath);
54 scoped_ptr<base::DictionaryValue> host_config(
55 HostConfigFromJsonFile(config_path));
59 scoped_ptr<base::DictionaryValue> config(new base::DictionaryValue);
61 if (host_config->GetString(kHostIdConfigPath, &value))
62 config->SetString(kHostIdConfigPath, value);
63 if (host_config->GetString(kXmppLoginConfigPath, &value))
64 config->SetString(kXmppLoginConfigPath, value);
68 void DaemonControllerDelegateMac::SetConfigAndStart(
69 scoped_ptr<base::DictionaryValue> config,
71 const DaemonController::CompletionCallback& done) {
72 config->SetBoolean(kUsageStatsConsentConfigPath, consent);
73 ShowPreferencePane(HostConfigToJson(*config), done);
76 void DaemonControllerDelegateMac::UpdateConfig(
77 scoped_ptr<base::DictionaryValue> config,
78 const DaemonController::CompletionCallback& done) {
79 base::FilePath config_file_path(kHostConfigFilePath);
80 scoped_ptr<base::DictionaryValue> host_config(
81 HostConfigFromJsonFile(config_file_path));
83 done.Run(DaemonController::RESULT_FAILED);
87 host_config->MergeDictionary(config.get());
88 ShowPreferencePane(HostConfigToJson(*host_config), done);
91 void DaemonControllerDelegateMac::Stop(
92 const DaemonController::CompletionCallback& done) {
93 ShowPreferencePane("", done);
96 DaemonController::UsageStatsConsent
97 DaemonControllerDelegateMac::GetUsageStatsConsent() {
98 DaemonController::UsageStatsConsent consent;
99 consent.supported = true;
100 consent.allowed = false;
101 // set_by_policy is not yet supported.
102 consent.set_by_policy = false;
104 base::FilePath config_file_path(kHostConfigFilePath);
105 scoped_ptr<base::DictionaryValue> host_config(
106 HostConfigFromJsonFile(config_file_path));
108 host_config->GetBoolean(kUsageStatsConsentConfigPath, &consent.allowed);
114 void DaemonControllerDelegateMac::ShowPreferencePane(
115 const std::string& config_data,
116 const DaemonController::CompletionCallback& done) {
117 if (DoShowPreferencePane(config_data)) {
118 RegisterForPreferencePaneNotifications(done);
120 done.Run(DaemonController::RESULT_FAILED);
124 // CFNotificationCenterAddObserver ties the thread on which distributed
125 // notifications are received to the one on which it is first called.
126 // This is safe because HostNPScriptObject::InvokeAsyncResultCallback
127 // bounces the invocation to the correct thread, so it doesn't matter
128 // which thread CompletionCallbacks are called on.
129 void DaemonControllerDelegateMac::RegisterForPreferencePaneNotifications(
130 const DaemonController::CompletionCallback& done) {
131 // We can only have one callback registered at a time. This is enforced by the
132 // UX flow of the web-app.
133 DCHECK(current_callback_.is_null());
134 current_callback_ = done;
136 CFNotificationCenterAddObserver(
137 CFNotificationCenterGetDistributedCenter(),
139 &DaemonControllerDelegateMac::PreferencePaneCallback,
140 CFSTR(UPDATE_SUCCEEDED_NOTIFICATION_NAME),
142 CFNotificationSuspensionBehaviorDeliverImmediately);
143 CFNotificationCenterAddObserver(
144 CFNotificationCenterGetDistributedCenter(),
146 &DaemonControllerDelegateMac::PreferencePaneCallback,
147 CFSTR(UPDATE_FAILED_NOTIFICATION_NAME),
149 CFNotificationSuspensionBehaviorDeliverImmediately);
152 void DaemonControllerDelegateMac::DeregisterForPreferencePaneNotifications() {
153 CFNotificationCenterRemoveObserver(
154 CFNotificationCenterGetDistributedCenter(),
156 CFSTR(UPDATE_SUCCEEDED_NOTIFICATION_NAME),
158 CFNotificationCenterRemoveObserver(
159 CFNotificationCenterGetDistributedCenter(),
161 CFSTR(UPDATE_FAILED_NOTIFICATION_NAME),
165 void DaemonControllerDelegateMac::PreferencePaneCallbackDelegate(
167 DaemonController::AsyncResult result = DaemonController::RESULT_FAILED;
168 if (CFStringCompare(name, CFSTR(UPDATE_SUCCEEDED_NOTIFICATION_NAME), 0) ==
170 result = DaemonController::RESULT_OK;
171 } else if (CFStringCompare(name, CFSTR(UPDATE_FAILED_NOTIFICATION_NAME), 0) ==
173 result = DaemonController::RESULT_FAILED;
175 LOG(WARNING) << "Ignoring unexpected notification: " << name;
179 DeregisterForPreferencePaneNotifications();
181 DCHECK(!current_callback_.is_null());
182 base::ResetAndReturn(¤t_callback_).Run(result);
186 bool DaemonControllerDelegateMac::DoShowPreferencePane(
187 const std::string& config_data) {
188 if (!config_data.empty()) {
189 base::FilePath config_path;
190 if (!base::GetTempDir(&config_path)) {
191 LOG(ERROR) << "Failed to get filename for saving configuration data.";
194 config_path = config_path.Append(kHostConfigFileName);
196 int written = base::WriteFile(config_path, config_data.data(),
198 if (written != static_cast<int>(config_data.size())) {
199 LOG(ERROR) << "Failed to save configuration data to: "
200 << config_path.value();
205 base::FilePath pane_path;
206 // TODO(lambroslambrou): Use NSPreferencePanesDirectory once we start
207 // building against SDK 10.6.
208 if (!base::mac::GetLocalDirectory(NSLibraryDirectory, &pane_path)) {
209 LOG(ERROR) << "Failed to get directory for local preference panes.";
212 pane_path = pane_path.Append("PreferencePanes").Append(kPrefPaneFileName);
215 if (!base::mac::FSRefFromPath(pane_path.value(), &pane_path_ref)) {
216 LOG(ERROR) << "Failed to create FSRef";
219 OSStatus status = LSOpenFSRef(&pane_path_ref, nullptr);
220 if (status != noErr) {
221 OSSTATUS_LOG(ERROR, status) << "LSOpenFSRef failed for path: "
222 << pane_path.value();
226 CFNotificationCenterRef center =
227 CFNotificationCenterGetDistributedCenter();
228 base::ScopedCFTypeRef<CFStringRef> service_name(CFStringCreateWithCString(
229 kCFAllocatorDefault, remoting::kServiceName, kCFStringEncodingUTF8));
230 CFNotificationCenterPostNotification(center, service_name, nullptr, nullptr,
236 void DaemonControllerDelegateMac::PreferencePaneCallback(
237 CFNotificationCenterRef center,
241 CFDictionaryRef user_info) {
242 DaemonControllerDelegateMac* self =
243 reinterpret_cast<DaemonControllerDelegateMac*>(observer);
245 LOG(WARNING) << "Ignoring notification with nullptr observer: " << name;
249 self->PreferencePaneCallbackDelegate(name);
252 scoped_refptr<DaemonController> DaemonController::Create() {
253 scoped_ptr<DaemonController::Delegate> delegate(
254 new DaemonControllerDelegateMac());
255 return new DaemonController(delegate.Pass());
258 } // namespace remoting