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/file_util.h"
17 #include "base/files/file_path.h"
18 #include "base/json/json_writer.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/json_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_NOT_INSTALLED;
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 JsonHostConfig host_config(config_path);
55 scoped_ptr<base::DictionaryValue> config;
57 if (host_config.Read()) {
58 config.reset(new base::DictionaryValue());
60 if (host_config.GetString(kHostIdConfigPath, &value))
61 config.get()->SetString(kHostIdConfigPath, value);
62 if (host_config.GetString(kXmppLoginConfigPath, &value))
63 config.get()->SetString(kXmppLoginConfigPath, value);
69 void DaemonControllerDelegateMac::InstallHost(
70 const DaemonController::CompletionCallback& done) {
74 void DaemonControllerDelegateMac::SetConfigAndStart(
75 scoped_ptr<base::DictionaryValue> config,
77 const DaemonController::CompletionCallback& done) {
78 config->SetBoolean(kUsageStatsConsentConfigPath, consent);
79 std::string config_data;
80 base::JSONWriter::Write(config.get(), &config_data);
81 ShowPreferencePane(config_data, done);
84 void DaemonControllerDelegateMac::UpdateConfig(
85 scoped_ptr<base::DictionaryValue> config,
86 const DaemonController::CompletionCallback& done) {
87 base::FilePath config_file_path(kHostConfigFilePath);
88 JsonHostConfig config_file(config_file_path);
89 if (!config_file.Read()) {
90 done.Run(DaemonController::RESULT_FAILED);
93 if (!config_file.CopyFrom(config.get())) {
94 LOG(ERROR) << "Failed to update configuration.";
95 done.Run(DaemonController::RESULT_FAILED);
99 std::string config_data = config_file.GetSerializedData();
100 ShowPreferencePane(config_data, done);
103 void DaemonControllerDelegateMac::Stop(
104 const DaemonController::CompletionCallback& done) {
105 ShowPreferencePane("", done);
108 void DaemonControllerDelegateMac::SetWindow(void* window_handle) {
112 std::string DaemonControllerDelegateMac::GetVersion() {
113 std::string version = "";
114 std::string command_line = remoting::kHostHelperScriptPath;
115 command_line += " --host-version";
116 FILE* script_output = popen(command_line.c_str(), "r");
119 char* result = fgets(buffer, sizeof(buffer), script_output);
120 pclose(script_output);
122 // The string is guaranteed to be null-terminated, but probably contains
123 // a newline character, which we don't want.
124 for (int i = 0; result[i]; ++i) {
125 if (result[i] < ' ') {
137 DaemonController::UsageStatsConsent
138 DaemonControllerDelegateMac::GetUsageStatsConsent() {
139 DaemonController::UsageStatsConsent consent;
140 consent.supported = true;
141 consent.allowed = false;
142 // set_by_policy is not yet supported.
143 consent.set_by_policy = false;
145 base::FilePath config_file_path(kHostConfigFilePath);
146 JsonHostConfig host_config(config_file_path);
147 if (host_config.Read()) {
148 host_config.GetBoolean(kUsageStatsConsentConfigPath, &consent.allowed);
154 void DaemonControllerDelegateMac::ShowPreferencePane(
155 const std::string& config_data,
156 const DaemonController::CompletionCallback& done) {
157 if (DoShowPreferencePane(config_data)) {
158 RegisterForPreferencePaneNotifications(done);
160 done.Run(DaemonController::RESULT_FAILED);
164 // CFNotificationCenterAddObserver ties the thread on which distributed
165 // notifications are received to the one on which it is first called.
166 // This is safe because HostNPScriptObject::InvokeAsyncResultCallback
167 // bounces the invocation to the correct thread, so it doesn't matter
168 // which thread CompletionCallbacks are called on.
169 void DaemonControllerDelegateMac::RegisterForPreferencePaneNotifications(
170 const DaemonController::CompletionCallback& done) {
171 // We can only have one callback registered at a time. This is enforced by the
172 // UX flow of the web-app.
173 DCHECK(current_callback_.is_null());
174 current_callback_ = done;
176 CFNotificationCenterAddObserver(
177 CFNotificationCenterGetDistributedCenter(),
179 &DaemonControllerDelegateMac::PreferencePaneCallback,
180 CFSTR(UPDATE_SUCCEEDED_NOTIFICATION_NAME),
182 CFNotificationSuspensionBehaviorDeliverImmediately);
183 CFNotificationCenterAddObserver(
184 CFNotificationCenterGetDistributedCenter(),
186 &DaemonControllerDelegateMac::PreferencePaneCallback,
187 CFSTR(UPDATE_FAILED_NOTIFICATION_NAME),
189 CFNotificationSuspensionBehaviorDeliverImmediately);
192 void DaemonControllerDelegateMac::DeregisterForPreferencePaneNotifications() {
193 CFNotificationCenterRemoveObserver(
194 CFNotificationCenterGetDistributedCenter(),
196 CFSTR(UPDATE_SUCCEEDED_NOTIFICATION_NAME),
198 CFNotificationCenterRemoveObserver(
199 CFNotificationCenterGetDistributedCenter(),
201 CFSTR(UPDATE_FAILED_NOTIFICATION_NAME),
205 void DaemonControllerDelegateMac::PreferencePaneCallbackDelegate(
207 DaemonController::AsyncResult result = DaemonController::RESULT_FAILED;
208 if (CFStringCompare(name, CFSTR(UPDATE_SUCCEEDED_NOTIFICATION_NAME), 0) ==
210 result = DaemonController::RESULT_OK;
211 } else if (CFStringCompare(name, CFSTR(UPDATE_FAILED_NOTIFICATION_NAME), 0) ==
213 result = DaemonController::RESULT_FAILED;
215 LOG(WARNING) << "Ignoring unexpected notification: " << name;
219 DCHECK(!current_callback_.is_null());
220 DaemonController::CompletionCallback done = current_callback_;
221 current_callback_.Reset();
224 DeregisterForPreferencePaneNotifications();
228 bool DaemonControllerDelegateMac::DoShowPreferencePane(
229 const std::string& config_data) {
230 if (!config_data.empty()) {
231 base::FilePath config_path;
232 if (!base::GetTempDir(&config_path)) {
233 LOG(ERROR) << "Failed to get filename for saving configuration data.";
236 config_path = config_path.Append(kHostConfigFileName);
238 int written = base::WriteFile(config_path, config_data.data(),
240 if (written != static_cast<int>(config_data.size())) {
241 LOG(ERROR) << "Failed to save configuration data to: "
242 << config_path.value();
247 base::FilePath pane_path;
248 // TODO(lambroslambrou): Use NSPreferencePanesDirectory once we start
249 // building against SDK 10.6.
250 if (!base::mac::GetLocalDirectory(NSLibraryDirectory, &pane_path)) {
251 LOG(ERROR) << "Failed to get directory for local preference panes.";
254 pane_path = pane_path.Append("PreferencePanes").Append(kPrefPaneFileName);
257 if (!base::mac::FSRefFromPath(pane_path.value(), &pane_path_ref)) {
258 LOG(ERROR) << "Failed to create FSRef";
261 OSStatus status = LSOpenFSRef(&pane_path_ref, NULL);
262 if (status != noErr) {
263 OSSTATUS_LOG(ERROR, status) << "LSOpenFSRef failed for path: "
264 << pane_path.value();
268 CFNotificationCenterRef center =
269 CFNotificationCenterGetDistributedCenter();
270 base::ScopedCFTypeRef<CFStringRef> service_name(CFStringCreateWithCString(
271 kCFAllocatorDefault, remoting::kServiceName, kCFStringEncodingUTF8));
272 CFNotificationCenterPostNotification(center, service_name, NULL, NULL,
278 void DaemonControllerDelegateMac::PreferencePaneCallback(
279 CFNotificationCenterRef center,
283 CFDictionaryRef user_info) {
284 DaemonControllerDelegateMac* self =
285 reinterpret_cast<DaemonControllerDelegateMac*>(observer);
287 LOG(WARNING) << "Ignoring notification with NULL observer: " << name;
291 self->PreferencePaneCallbackDelegate(name);
294 scoped_refptr<DaemonController> DaemonController::Create() {
295 scoped_ptr<DaemonController::Delegate> delegate(
296 new DaemonControllerDelegateMac());
297 return new DaemonController(delegate.Pass());
300 } // namespace remoting