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_NOT_INSTALLED;
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::InstallHost(
68 const DaemonController::CompletionCallback& done) {
72 void DaemonControllerDelegateMac::SetConfigAndStart(
73 scoped_ptr<base::DictionaryValue> config,
75 const DaemonController::CompletionCallback& done) {
76 config->SetBoolean(kUsageStatsConsentConfigPath, consent);
77 ShowPreferencePane(HostConfigToJson(*config), done);
80 void DaemonControllerDelegateMac::UpdateConfig(
81 scoped_ptr<base::DictionaryValue> config,
82 const DaemonController::CompletionCallback& done) {
83 base::FilePath config_file_path(kHostConfigFilePath);
84 scoped_ptr<base::DictionaryValue> host_config(
85 HostConfigFromJsonFile(config_file_path));
87 done.Run(DaemonController::RESULT_FAILED);
91 host_config->MergeDictionary(config.get());
92 ShowPreferencePane(HostConfigToJson(*host_config), done);
95 void DaemonControllerDelegateMac::Stop(
96 const DaemonController::CompletionCallback& done) {
97 ShowPreferencePane("", done);
100 void DaemonControllerDelegateMac::SetWindow(void* window_handle) {
104 std::string DaemonControllerDelegateMac::GetVersion() {
105 std::string version = "";
106 std::string command_line = remoting::kHostHelperScriptPath;
107 command_line += " --host-version";
108 FILE* script_output = popen(command_line.c_str(), "r");
111 char* result = fgets(buffer, sizeof(buffer), script_output);
112 pclose(script_output);
114 // The string is guaranteed to be null-terminated, but probably contains
115 // a newline character, which we don't want.
116 for (int i = 0; result[i]; ++i) {
117 if (result[i] < ' ') {
129 DaemonController::UsageStatsConsent
130 DaemonControllerDelegateMac::GetUsageStatsConsent() {
131 DaemonController::UsageStatsConsent consent;
132 consent.supported = true;
133 consent.allowed = false;
134 // set_by_policy is not yet supported.
135 consent.set_by_policy = false;
137 base::FilePath config_file_path(kHostConfigFilePath);
138 scoped_ptr<base::DictionaryValue> host_config(
139 HostConfigFromJsonFile(config_file_path));
141 host_config->GetBoolean(kUsageStatsConsentConfigPath, &consent.allowed);
147 void DaemonControllerDelegateMac::ShowPreferencePane(
148 const std::string& config_data,
149 const DaemonController::CompletionCallback& done) {
150 if (DoShowPreferencePane(config_data)) {
151 RegisterForPreferencePaneNotifications(done);
153 done.Run(DaemonController::RESULT_FAILED);
157 // CFNotificationCenterAddObserver ties the thread on which distributed
158 // notifications are received to the one on which it is first called.
159 // This is safe because HostNPScriptObject::InvokeAsyncResultCallback
160 // bounces the invocation to the correct thread, so it doesn't matter
161 // which thread CompletionCallbacks are called on.
162 void DaemonControllerDelegateMac::RegisterForPreferencePaneNotifications(
163 const DaemonController::CompletionCallback& done) {
164 // We can only have one callback registered at a time. This is enforced by the
165 // UX flow of the web-app.
166 DCHECK(current_callback_.is_null());
167 current_callback_ = done;
169 CFNotificationCenterAddObserver(
170 CFNotificationCenterGetDistributedCenter(),
172 &DaemonControllerDelegateMac::PreferencePaneCallback,
173 CFSTR(UPDATE_SUCCEEDED_NOTIFICATION_NAME),
175 CFNotificationSuspensionBehaviorDeliverImmediately);
176 CFNotificationCenterAddObserver(
177 CFNotificationCenterGetDistributedCenter(),
179 &DaemonControllerDelegateMac::PreferencePaneCallback,
180 CFSTR(UPDATE_FAILED_NOTIFICATION_NAME),
182 CFNotificationSuspensionBehaviorDeliverImmediately);
185 void DaemonControllerDelegateMac::DeregisterForPreferencePaneNotifications() {
186 CFNotificationCenterRemoveObserver(
187 CFNotificationCenterGetDistributedCenter(),
189 CFSTR(UPDATE_SUCCEEDED_NOTIFICATION_NAME),
191 CFNotificationCenterRemoveObserver(
192 CFNotificationCenterGetDistributedCenter(),
194 CFSTR(UPDATE_FAILED_NOTIFICATION_NAME),
198 void DaemonControllerDelegateMac::PreferencePaneCallbackDelegate(
200 DaemonController::AsyncResult result = DaemonController::RESULT_FAILED;
201 if (CFStringCompare(name, CFSTR(UPDATE_SUCCEEDED_NOTIFICATION_NAME), 0) ==
203 result = DaemonController::RESULT_OK;
204 } else if (CFStringCompare(name, CFSTR(UPDATE_FAILED_NOTIFICATION_NAME), 0) ==
206 result = DaemonController::RESULT_FAILED;
208 LOG(WARNING) << "Ignoring unexpected notification: " << name;
212 DCHECK(!current_callback_.is_null());
213 DaemonController::CompletionCallback done = current_callback_;
214 current_callback_.Reset();
217 DeregisterForPreferencePaneNotifications();
221 bool DaemonControllerDelegateMac::DoShowPreferencePane(
222 const std::string& config_data) {
223 if (!config_data.empty()) {
224 base::FilePath config_path;
225 if (!base::GetTempDir(&config_path)) {
226 LOG(ERROR) << "Failed to get filename for saving configuration data.";
229 config_path = config_path.Append(kHostConfigFileName);
231 int written = base::WriteFile(config_path, config_data.data(),
233 if (written != static_cast<int>(config_data.size())) {
234 LOG(ERROR) << "Failed to save configuration data to: "
235 << config_path.value();
240 base::FilePath pane_path;
241 // TODO(lambroslambrou): Use NSPreferencePanesDirectory once we start
242 // building against SDK 10.6.
243 if (!base::mac::GetLocalDirectory(NSLibraryDirectory, &pane_path)) {
244 LOG(ERROR) << "Failed to get directory for local preference panes.";
247 pane_path = pane_path.Append("PreferencePanes").Append(kPrefPaneFileName);
250 if (!base::mac::FSRefFromPath(pane_path.value(), &pane_path_ref)) {
251 LOG(ERROR) << "Failed to create FSRef";
254 OSStatus status = LSOpenFSRef(&pane_path_ref, NULL);
255 if (status != noErr) {
256 OSSTATUS_LOG(ERROR, status) << "LSOpenFSRef failed for path: "
257 << pane_path.value();
261 CFNotificationCenterRef center =
262 CFNotificationCenterGetDistributedCenter();
263 base::ScopedCFTypeRef<CFStringRef> service_name(CFStringCreateWithCString(
264 kCFAllocatorDefault, remoting::kServiceName, kCFStringEncodingUTF8));
265 CFNotificationCenterPostNotification(center, service_name, NULL, NULL,
271 void DaemonControllerDelegateMac::PreferencePaneCallback(
272 CFNotificationCenterRef center,
276 CFDictionaryRef user_info) {
277 DaemonControllerDelegateMac* self =
278 reinterpret_cast<DaemonControllerDelegateMac*>(observer);
280 LOG(WARNING) << "Ignoring notification with NULL observer: " << name;
284 self->PreferencePaneCallbackDelegate(name);
287 scoped_refptr<DaemonController> DaemonController::Create() {
288 scoped_ptr<DaemonController::Delegate> delegate(
289 new DaemonControllerDelegateMac());
290 return new DaemonController(delegate.Pass());
293 } // namespace remoting