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/compiler_specific.h"
16 #include "base/files/file_path.h"
17 #include "base/files/file_util.h"
18 #include "base/logging.h"
19 #include "base/mac/foundation_util.h"
20 #include "base/mac/launchd.h"
21 #include "base/mac/mac_logging.h"
22 #include "base/mac/mac_util.h"
23 #include "base/mac/scoped_launch_data.h"
24 #include "base/time/time.h"
25 #include "base/values.h"
26 #include "remoting/host/constants_mac.h"
27 #include "remoting/host/host_config.h"
28 #include "remoting/host/usage_stats_consent.h"
32 DaemonControllerDelegateMac::DaemonControllerDelegateMac() {
35 DaemonControllerDelegateMac::~DaemonControllerDelegateMac() {
36 DeregisterForPreferencePaneNotifications();
39 DaemonController::State DaemonControllerDelegateMac::GetState() {
40 pid_t job_pid = base::mac::PIDForJob(kServiceName);
42 return DaemonController::STATE_UNKNOWN;
43 } else if (job_pid == 0) {
44 // Service is stopped, or a start attempt failed.
45 return DaemonController::STATE_STOPPED;
47 return DaemonController::STATE_STARTED;
51 scoped_ptr<base::DictionaryValue> DaemonControllerDelegateMac::GetConfig() {
52 base::FilePath config_path(kHostConfigFilePath);
53 scoped_ptr<base::DictionaryValue> host_config(
54 HostConfigFromJsonFile(config_path));
58 scoped_ptr<base::DictionaryValue> config(new base::DictionaryValue);
60 if (host_config->GetString(kHostIdConfigPath, &value))
61 config->SetString(kHostIdConfigPath, value);
62 if (host_config->GetString(kXmppLoginConfigPath, &value))
63 config->SetString(kXmppLoginConfigPath, value);
67 void DaemonControllerDelegateMac::SetConfigAndStart(
68 scoped_ptr<base::DictionaryValue> config,
70 const DaemonController::CompletionCallback& done) {
71 config->SetBoolean(kUsageStatsConsentConfigPath, consent);
72 ShowPreferencePane(HostConfigToJson(*config), done);
75 void DaemonControllerDelegateMac::UpdateConfig(
76 scoped_ptr<base::DictionaryValue> config,
77 const DaemonController::CompletionCallback& done) {
78 base::FilePath config_file_path(kHostConfigFilePath);
79 scoped_ptr<base::DictionaryValue> host_config(
80 HostConfigFromJsonFile(config_file_path));
82 done.Run(DaemonController::RESULT_FAILED);
86 host_config->MergeDictionary(config.get());
87 ShowPreferencePane(HostConfigToJson(*host_config), done);
90 void DaemonControllerDelegateMac::Stop(
91 const DaemonController::CompletionCallback& done) {
92 ShowPreferencePane("", done);
95 DaemonController::UsageStatsConsent
96 DaemonControllerDelegateMac::GetUsageStatsConsent() {
97 DaemonController::UsageStatsConsent consent;
98 consent.supported = true;
99 consent.allowed = false;
100 // set_by_policy is not yet supported.
101 consent.set_by_policy = false;
103 base::FilePath config_file_path(kHostConfigFilePath);
104 scoped_ptr<base::DictionaryValue> host_config(
105 HostConfigFromJsonFile(config_file_path));
107 host_config->GetBoolean(kUsageStatsConsentConfigPath, &consent.allowed);
113 void DaemonControllerDelegateMac::ShowPreferencePane(
114 const std::string& config_data,
115 const DaemonController::CompletionCallback& done) {
116 if (DoShowPreferencePane(config_data)) {
117 RegisterForPreferencePaneNotifications(done);
119 done.Run(DaemonController::RESULT_FAILED);
123 // CFNotificationCenterAddObserver ties the thread on which distributed
124 // notifications are received to the one on which it is first called.
125 // This is safe because HostNPScriptObject::InvokeAsyncResultCallback
126 // bounces the invocation to the correct thread, so it doesn't matter
127 // which thread CompletionCallbacks are called on.
128 void DaemonControllerDelegateMac::RegisterForPreferencePaneNotifications(
129 const DaemonController::CompletionCallback& done) {
130 // We can only have one callback registered at a time. This is enforced by the
131 // UX flow of the web-app.
132 DCHECK(current_callback_.is_null());
133 current_callback_ = done;
135 CFNotificationCenterAddObserver(
136 CFNotificationCenterGetDistributedCenter(),
138 &DaemonControllerDelegateMac::PreferencePaneCallback,
139 CFSTR(UPDATE_SUCCEEDED_NOTIFICATION_NAME),
141 CFNotificationSuspensionBehaviorDeliverImmediately);
142 CFNotificationCenterAddObserver(
143 CFNotificationCenterGetDistributedCenter(),
145 &DaemonControllerDelegateMac::PreferencePaneCallback,
146 CFSTR(UPDATE_FAILED_NOTIFICATION_NAME),
148 CFNotificationSuspensionBehaviorDeliverImmediately);
151 void DaemonControllerDelegateMac::DeregisterForPreferencePaneNotifications() {
152 CFNotificationCenterRemoveObserver(
153 CFNotificationCenterGetDistributedCenter(),
155 CFSTR(UPDATE_SUCCEEDED_NOTIFICATION_NAME),
157 CFNotificationCenterRemoveObserver(
158 CFNotificationCenterGetDistributedCenter(),
160 CFSTR(UPDATE_FAILED_NOTIFICATION_NAME),
164 void DaemonControllerDelegateMac::PreferencePaneCallbackDelegate(
166 DaemonController::AsyncResult result = DaemonController::RESULT_FAILED;
167 if (CFStringCompare(name, CFSTR(UPDATE_SUCCEEDED_NOTIFICATION_NAME), 0) ==
169 result = DaemonController::RESULT_OK;
170 } else if (CFStringCompare(name, CFSTR(UPDATE_FAILED_NOTIFICATION_NAME), 0) ==
172 result = DaemonController::RESULT_FAILED;
174 LOG(WARNING) << "Ignoring unexpected notification: " << name;
178 DCHECK(!current_callback_.is_null());
179 DaemonController::CompletionCallback done = current_callback_;
180 current_callback_.Reset();
183 DeregisterForPreferencePaneNotifications();
187 bool DaemonControllerDelegateMac::DoShowPreferencePane(
188 const std::string& config_data) {
189 if (!config_data.empty()) {
190 base::FilePath config_path;
191 if (!base::GetTempDir(&config_path)) {
192 LOG(ERROR) << "Failed to get filename for saving configuration data.";
195 config_path = config_path.Append(kHostConfigFileName);
197 int written = base::WriteFile(config_path, config_data.data(),
199 if (written != static_cast<int>(config_data.size())) {
200 LOG(ERROR) << "Failed to save configuration data to: "
201 << config_path.value();
206 base::FilePath pane_path;
207 // TODO(lambroslambrou): Use NSPreferencePanesDirectory once we start
208 // building against SDK 10.6.
209 if (!base::mac::GetLocalDirectory(NSLibraryDirectory, &pane_path)) {
210 LOG(ERROR) << "Failed to get directory for local preference panes.";
213 pane_path = pane_path.Append("PreferencePanes").Append(kPrefPaneFileName);
216 if (!base::mac::FSRefFromPath(pane_path.value(), &pane_path_ref)) {
217 LOG(ERROR) << "Failed to create FSRef";
220 OSStatus status = LSOpenFSRef(&pane_path_ref, nullptr);
221 if (status != noErr) {
222 OSSTATUS_LOG(ERROR, status) << "LSOpenFSRef failed for path: "
223 << pane_path.value();
227 CFNotificationCenterRef center =
228 CFNotificationCenterGetDistributedCenter();
229 base::ScopedCFTypeRef<CFStringRef> service_name(CFStringCreateWithCString(
230 kCFAllocatorDefault, remoting::kServiceName, kCFStringEncodingUTF8));
231 CFNotificationCenterPostNotification(center, service_name, nullptr, nullptr,
237 void DaemonControllerDelegateMac::PreferencePaneCallback(
238 CFNotificationCenterRef center,
242 CFDictionaryRef user_info) {
243 DaemonControllerDelegateMac* self =
244 reinterpret_cast<DaemonControllerDelegateMac*>(observer);
246 LOG(WARNING) << "Ignoring notification with nullptr observer: " << name;
250 self->PreferencePaneCallbackDelegate(name);
253 scoped_refptr<DaemonController> DaemonController::Create() {
254 scoped_ptr<DaemonController::Delegate> delegate(
255 new DaemonControllerDelegateMac());
256 return new DaemonController(delegate.Pass());
259 } // namespace remoting