Index: remoting/host/setup/daemon_controller_delegate_mac.mm |
diff --git a/remoting/host/setup/daemon_controller_delegate_mac.mm b/remoting/host/setup/daemon_controller_delegate_mac.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..749a6ab6a31667f0e914fadaad52ec1825d22d72 |
--- /dev/null |
+++ b/remoting/host/setup/daemon_controller_delegate_mac.mm |
@@ -0,0 +1,295 @@ |
+// Copyright 2013 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include <CoreFoundation/CoreFoundation.h> |
+ |
+#include "remoting/host/setup/daemon_controller_delegate_mac.h" |
+ |
+#include <launch.h> |
+#include <stdio.h> |
+#include <sys/types.h> |
+ |
+#include "base/basictypes.h" |
+#include "base/bind.h" |
+#include "base/compiler_specific.h" |
+#include "base/file_util.h" |
+#include "base/files/file_path.h" |
+#include "base/json/json_writer.h" |
+#include "base/logging.h" |
+#include "base/mac/foundation_util.h" |
+#include "base/mac/launchd.h" |
+#include "base/mac/mac_logging.h" |
+#include "base/mac/mac_util.h" |
+#include "base/mac/scoped_launch_data.h" |
+#include "base/time/time.h" |
+#include "base/values.h" |
+#include "remoting/host/constants_mac.h" |
+#include "remoting/host/json_host_config.h" |
+#include "remoting/host/usage_stats_consent.h" |
+ |
+namespace remoting { |
+ |
+DaemonControllerDelegateMac::DaemonControllerDelegateMac() { |
+} |
+ |
+DaemonControllerDelegateMac::~DaemonControllerDelegateMac() { |
+ DeregisterForPreferencePaneNotifications(); |
+} |
+ |
+DaemonController::State DaemonControllerDelegateMac::GetState() { |
+ pid_t job_pid = base::mac::PIDForJob(kServiceName); |
+ if (job_pid < 0) { |
+ return DaemonController::STATE_NOT_INSTALLED; |
+ } else if (job_pid == 0) { |
+ // Service is stopped, or a start attempt failed. |
+ return DaemonController::STATE_STOPPED; |
+ } else { |
+ return DaemonController::STATE_STARTED; |
+ } |
+} |
+ |
+scoped_ptr<base::DictionaryValue> DaemonControllerDelegateMac::GetConfig() { |
+ base::FilePath config_path(kHostConfigFilePath); |
+ JsonHostConfig host_config(config_path); |
+ scoped_ptr<base::DictionaryValue> config; |
+ |
+ if (host_config.Read()) { |
+ config.reset(new base::DictionaryValue()); |
+ std::string value; |
+ if (host_config.GetString(kHostIdConfigPath, &value)) |
+ config.get()->SetString(kHostIdConfigPath, value); |
+ if (host_config.GetString(kXmppLoginConfigPath, &value)) |
+ config.get()->SetString(kXmppLoginConfigPath, value); |
+ } |
+ |
+ return config.Pass(); |
+} |
+ |
+void DaemonControllerDelegateMac::SetConfigAndStart( |
+ scoped_ptr<base::DictionaryValue> config, |
+ bool consent, |
+ const DaemonController::CompletionCallback& done) { |
+ config->SetBoolean(kUsageStatsConsentConfigPath, consent); |
+ std::string config_data; |
+ base::JSONWriter::Write(config.get(), &config_data); |
+ ShowPreferencePane(config_data, done); |
+} |
+ |
+void DaemonControllerDelegateMac::UpdateConfig( |
+ scoped_ptr<base::DictionaryValue> config, |
+ const DaemonController::CompletionCallback& done) { |
+ base::FilePath config_file_path(kHostConfigFilePath); |
+ JsonHostConfig config_file(config_file_path); |
+ if (!config_file.Read()) { |
+ done.Run(DaemonController::RESULT_FAILED); |
+ return; |
+ } |
+ if (!config_file.CopyFrom(config.get())) { |
+ LOG(ERROR) << "Failed to update configuration."; |
+ done.Run(DaemonController::RESULT_FAILED); |
+ return; |
+ } |
+ |
+ std::string config_data = config_file.GetSerializedData(); |
+ ShowPreferencePane(config_data, done); |
+} |
+ |
+void DaemonControllerDelegateMac::Stop( |
+ const DaemonController::CompletionCallback& done) { |
+ ShowPreferencePane("", done); |
+} |
+ |
+void DaemonControllerDelegateMac::SetWindow(void* window_handle) { |
+ // noop |
+} |
+ |
+std::string DaemonControllerDelegateMac::GetVersion() { |
+ std::string version = ""; |
+ std::string command_line = remoting::kHostHelperScriptPath; |
+ command_line += " --host-version"; |
+ FILE* script_output = popen(command_line.c_str(), "r"); |
+ if (script_output) { |
+ char buffer[100]; |
+ char* result = fgets(buffer, sizeof(buffer), script_output); |
+ pclose(script_output); |
+ if (result) { |
+ // The string is guaranteed to be null-terminated, but probably contains |
+ // a newline character, which we don't want. |
+ for (int i = 0; result[i]; ++i) { |
+ if (result[i] < ' ') { |
+ result[i] = 0; |
+ break; |
+ } |
+ } |
+ version = result; |
+ } |
+ } |
+ |
+ return version; |
+} |
+ |
+DaemonController::UsageStatsConsent |
+DaemonControllerDelegateMac::GetUsageStatsConsent() { |
+ DaemonController::UsageStatsConsent consent; |
+ consent.supported = true; |
+ consent.allowed = false; |
+ // set_by_policy is not yet supported. |
+ consent.set_by_policy = false; |
+ |
+ base::FilePath config_file_path(kHostConfigFilePath); |
+ JsonHostConfig host_config(config_file_path); |
+ if (host_config.Read()) { |
+ host_config.GetBoolean(kUsageStatsConsentConfigPath, &consent.allowed); |
+ } |
+ |
+ return consent; |
+} |
+ |
+void DaemonControllerDelegateMac::ShowPreferencePane( |
+ const std::string& config_data, |
+ const DaemonController::CompletionCallback& done) { |
+ if (DoShowPreferencePane(config_data)) { |
+ RegisterForPreferencePaneNotifications(done); |
+ } else { |
+ done.Run(DaemonController::RESULT_FAILED); |
+ } |
+} |
+ |
+// CFNotificationCenterAddObserver ties the thread on which distributed |
+// notifications are received to the one on which it is first called. |
+// This is safe because HostNPScriptObject::InvokeAsyncResultCallback |
+// bounces the invocation to the correct thread, so it doesn't matter |
+// which thread CompletionCallbacks are called on. |
+void DaemonControllerDelegateMac::RegisterForPreferencePaneNotifications( |
+ const DaemonController::CompletionCallback& done) { |
+ // We can only have one callback registered at a time. This is enforced by the |
+ // UX flow of the web-app. |
+ DCHECK(current_callback_.is_null()); |
+ current_callback_ = done; |
+ |
+ CFNotificationCenterAddObserver( |
+ CFNotificationCenterGetDistributedCenter(), |
+ this, |
+ &DaemonControllerDelegateMac::PreferencePaneCallback, |
+ CFSTR(UPDATE_SUCCEEDED_NOTIFICATION_NAME), |
+ NULL, |
+ CFNotificationSuspensionBehaviorDeliverImmediately); |
+ CFNotificationCenterAddObserver( |
+ CFNotificationCenterGetDistributedCenter(), |
+ this, |
+ &DaemonControllerDelegateMac::PreferencePaneCallback, |
+ CFSTR(UPDATE_FAILED_NOTIFICATION_NAME), |
+ NULL, |
+ CFNotificationSuspensionBehaviorDeliverImmediately); |
+} |
+ |
+void DaemonControllerDelegateMac::DeregisterForPreferencePaneNotifications() { |
+ CFNotificationCenterRemoveObserver( |
+ CFNotificationCenterGetDistributedCenter(), |
+ this, |
+ CFSTR(UPDATE_SUCCEEDED_NOTIFICATION_NAME), |
+ NULL); |
+ CFNotificationCenterRemoveObserver( |
+ CFNotificationCenterGetDistributedCenter(), |
+ this, |
+ CFSTR(UPDATE_FAILED_NOTIFICATION_NAME), |
+ NULL); |
+} |
+ |
+void DaemonControllerDelegateMac::PreferencePaneCallbackDelegate( |
+ CFStringRef name) { |
+ DaemonController::AsyncResult result = DaemonController::RESULT_FAILED; |
+ if (CFStringCompare(name, CFSTR(UPDATE_SUCCEEDED_NOTIFICATION_NAME), 0) == |
+ kCFCompareEqualTo) { |
+ result = DaemonController::RESULT_OK; |
+ } else if (CFStringCompare(name, CFSTR(UPDATE_FAILED_NOTIFICATION_NAME), 0) == |
+ kCFCompareEqualTo) { |
+ result = DaemonController::RESULT_FAILED; |
+ } else { |
+ LOG(WARNING) << "Ignoring unexpected notification: " << name; |
+ return; |
+ } |
+ |
+ DCHECK(!current_callback_.is_null()); |
+ DaemonController::CompletionCallback done = current_callback_; |
+ current_callback_.Reset(); |
+ done.Run(result); |
+ |
+ DeregisterForPreferencePaneNotifications(); |
+} |
+ |
+// static |
+bool DaemonControllerDelegateMac::DoShowPreferencePane( |
+ const std::string& config_data) { |
+ if (!config_data.empty()) { |
+ base::FilePath config_path; |
+ if (!file_util::GetTempDir(&config_path)) { |
+ LOG(ERROR) << "Failed to get filename for saving configuration data."; |
+ return false; |
+ } |
+ config_path = config_path.Append(kHostConfigFileName); |
+ |
+ int written = file_util::WriteFile(config_path, config_data.data(), |
+ config_data.size()); |
+ if (written != static_cast<int>(config_data.size())) { |
+ LOG(ERROR) << "Failed to save configuration data to: " |
+ << config_path.value(); |
+ return false; |
+ } |
+ } |
+ |
+ base::FilePath pane_path; |
+ // TODO(lambroslambrou): Use NSPreferencePanesDirectory once we start |
+ // building against SDK 10.6. |
+ if (!base::mac::GetLocalDirectory(NSLibraryDirectory, &pane_path)) { |
+ LOG(ERROR) << "Failed to get directory for local preference panes."; |
+ return false; |
+ } |
+ pane_path = pane_path.Append("PreferencePanes").Append(kPrefPaneFileName); |
+ |
+ FSRef pane_path_ref; |
+ if (!base::mac::FSRefFromPath(pane_path.value(), &pane_path_ref)) { |
+ LOG(ERROR) << "Failed to create FSRef"; |
+ return false; |
+ } |
+ OSStatus status = LSOpenFSRef(&pane_path_ref, NULL); |
+ if (status != noErr) { |
+ OSSTATUS_LOG(ERROR, status) << "LSOpenFSRef failed for path: " |
+ << pane_path.value(); |
+ return false; |
+ } |
+ |
+ CFNotificationCenterRef center = |
+ CFNotificationCenterGetDistributedCenter(); |
+ base::ScopedCFTypeRef<CFStringRef> service_name(CFStringCreateWithCString( |
+ kCFAllocatorDefault, remoting::kServiceName, kCFStringEncodingUTF8)); |
+ CFNotificationCenterPostNotification(center, service_name, NULL, NULL, |
+ TRUE); |
+ return true; |
+} |
+ |
+// static |
+void DaemonControllerDelegateMac::PreferencePaneCallback( |
+ CFNotificationCenterRef center, |
+ void* observer, |
+ CFStringRef name, |
+ const void* object, |
+ CFDictionaryRef user_info) { |
+ DaemonControllerDelegateMac* self = |
+ reinterpret_cast<DaemonControllerDelegateMac*>(observer); |
+ if (!self) { |
+ LOG(WARNING) << "Ignoring notification with NULL observer: " << name; |
+ return; |
+ } |
+ |
+ self->PreferencePaneCallbackDelegate(name); |
+} |
+ |
+scoped_refptr<DaemonController> DaemonController::Create() { |
+ scoped_ptr<DaemonController::Delegate> delegate( |
+ new DaemonControllerDelegateMac()); |
+ return new DaemonController(delegate.Pass()); |
+} |
+ |
+} // namespace remoting |