// Copyright (c) 2012 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 "chromeos/ime/ibus_daemon_controller.h" #include "base/bind.h" #include "base/chromeos/chromeos_version.h" #include "base/environment.h" #include "base/files/file_path_watcher.h" #include "base/location.h" #include "base/logging.h" #include "base/observer_list.h" #include "base/process/launch.h" #include "base/process/process_handle.h" #include "base/rand_util.h" #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" #include "base/threading/thread_checker.h" #include "chromeos/dbus/dbus_thread_manager.h" namespace chromeos { namespace { IBusDaemonController* g_ibus_daemon_controller = NULL; base::FilePathWatcher* g_file_path_watcher = NULL; // Called when the ibus-daemon address file is modified. static void OnFilePathChanged( const scoped_refptr ui_task_runner, const base::Closure& closure, const base::FilePath& file_path, bool failed) { if (failed) return; // Can't recover, do nothing. if (!g_file_path_watcher) return; // Already discarded watch task. ui_task_runner->PostTask(FROM_HERE, closure); ui_task_runner->DeleteSoon(FROM_HERE, g_file_path_watcher); g_file_path_watcher = NULL; } // Start watching |address_file_path|. If the target file is changed, |callback| // is called on UI thread. This function should be called on FILE thread. void StartWatch( const std::string& address_file_path, const base::Closure& closure, const scoped_refptr& ui_task_runner) { // Before start watching, discard on-going watching task. delete g_file_path_watcher; g_file_path_watcher = new base::FilePathWatcher; bool result = g_file_path_watcher->Watch( base::FilePath::FromUTF8Unsafe(address_file_path), false, // do not watch child directory. base::Bind(&OnFilePathChanged, ui_task_runner, closure)); DCHECK(result); } // The implementation of IBusDaemonController. class IBusDaemonControllerImpl : public IBusDaemonController { public: // Represents current ibus-daemon status. enum IBusDaemonStatus { IBUS_DAEMON_INITIALIZING, IBUS_DAEMON_RUNNING, IBUS_DAEMON_SHUTTING_DOWN, IBUS_DAEMON_STOP, }; IBusDaemonControllerImpl( const scoped_refptr& ui_task_runner, const scoped_refptr& file_task_runner) : process_handle_(base::kNullProcessHandle), ibus_daemon_status_(IBUS_DAEMON_STOP), ui_task_runner_(ui_task_runner), file_task_runner_(file_task_runner), weak_ptr_factory_(this) { } virtual ~IBusDaemonControllerImpl() {} // IBusDaemonController override: virtual void AddObserver(Observer* observer) OVERRIDE { DCHECK(thread_checker_.CalledOnValidThread()); observers_.AddObserver(observer); } // IBusDaemonController override: virtual void RemoveObserver(Observer* observer) OVERRIDE { DCHECK(thread_checker_.CalledOnValidThread()); observers_.RemoveObserver(observer); } // IBusDaemonController override: virtual bool Start() OVERRIDE { DCHECK(thread_checker_.CalledOnValidThread()); if (ibus_daemon_status_ == IBUS_DAEMON_RUNNING) return true; if (ibus_daemon_status_ == IBUS_DAEMON_STOP || ibus_daemon_status_ == IBUS_DAEMON_SHUTTING_DOWN) { return StartIBusDaemon(); } return true; } // IBusDaemonController override: virtual bool Stop() OVERRIDE { DCHECK(thread_checker_.CalledOnValidThread()); NOTREACHED() << "Termination of ibus-daemon is not supported" << "http://crosbug.com/27051"; return false; } private: // Starts ibus-daemon service. bool StartIBusDaemon() { if (ibus_daemon_status_ == IBUS_DAEMON_INITIALIZING || ibus_daemon_status_ == IBUS_DAEMON_RUNNING) { DVLOG(1) << "MaybeLaunchIBusDaemon: ibus-daemon is already running."; return false; } ibus_daemon_status_ = IBUS_DAEMON_INITIALIZING; ibus_daemon_address_ = base::StringPrintf( "unix:abstract=ibus-%d", base::RandInt(0, std::numeric_limits::max())); scoped_ptr env(base::Environment::Create()); std::string address_file_path; env->GetVar("IBUS_ADDRESS_FILE", &address_file_path); DCHECK(!address_file_path.empty()); // Set up ibus-daemon address file watcher before launching ibus-daemon, // because if watcher starts after ibus-daemon, we may miss the ibus // connection initialization. bool success = file_task_runner_->PostTaskAndReply( FROM_HERE, base::Bind(&StartWatch, address_file_path, base::Bind(&IBusDaemonControllerImpl::FilePathChanged, weak_ptr_factory_.GetWeakPtr(), ibus_daemon_address_), ui_task_runner_), base::Bind(&IBusDaemonControllerImpl::LaunchIBusDaemon, weak_ptr_factory_.GetWeakPtr(), ibus_daemon_address_)); DCHECK(success); return true; } // Launhes actual ibus-daemon process. void LaunchIBusDaemon(const std::string& ibus_address) { DCHECK(thread_checker_.CalledOnValidThread()); DCHECK_EQ(base::kNullProcessHandle, process_handle_); static const char kIBusDaemonPath[] = "/usr/bin/ibus-daemon"; // TODO(zork): Send output to /var/log/ibus.log std::vector ibus_daemon_command_line; ibus_daemon_command_line.push_back(kIBusDaemonPath); ibus_daemon_command_line.push_back("--panel=disable"); ibus_daemon_command_line.push_back("--cache=none"); ibus_daemon_command_line.push_back("--restart"); ibus_daemon_command_line.push_back("--replace"); ibus_daemon_command_line.push_back("--address=" + ibus_address); if (!base::LaunchProcess(ibus_daemon_command_line, base::LaunchOptions(), &process_handle_)) { LOG(WARNING) << "Could not launch: " << JoinString(ibus_daemon_command_line, " "); } } // Called by FilePathWatcher when the ibus-daemon address file is changed. // This function will be called on FILE thread. void FilePathChanged(const std::string& ibus_address) { ui_task_runner_->PostTask( FROM_HERE, base::Bind(&IBusDaemonControllerImpl::IBusDaemonInitializationDone, weak_ptr_factory_.GetWeakPtr(), ibus_address)); } // Called by FilePathChaged function, this function should be called on UI // thread. void IBusDaemonInitializationDone(const std::string& ibus_address) { if (ibus_daemon_address_ != ibus_address) return; if (ibus_daemon_status_ != IBUS_DAEMON_INITIALIZING) { // Stop() or OnIBusDaemonExit() has already been called. return; } DBusThreadManager::Get()->InitIBusBus( ibus_address, base::Bind(&IBusDaemonControllerImpl::OnIBusDaemonDisconnected, weak_ptr_factory_.GetWeakPtr(), base::GetProcId(process_handle_))); ibus_daemon_status_ = IBUS_DAEMON_RUNNING; FOR_EACH_OBSERVER(Observer, observers_, OnConnected()); VLOG(1) << "The ibus-daemon initialization is done."; } // Called when the connection with ibus-daemon is disconnected. void OnIBusDaemonDisconnected(base::ProcessId pid) { if (!chromeos::DBusThreadManager::Get()) return; // Expected disconnection at shutting down. do nothing. if (process_handle_ != base::kNullProcessHandle) { if (base::GetProcId(process_handle_) == pid) { // ibus-daemon crashed. // TODO(nona): Shutdown ibus-bus connection. process_handle_ = base::kNullProcessHandle; } else { // This condition is as follows. // 1. Called Stop (process_handle_ becomes null) // 2. Called LaunchProcess (process_handle_ becomes new instance) // 3. Callbacked OnIBusDaemonExit for old instance and reach here. // In this case, we should not reset process_handle_ as null, and do not // re-launch ibus-daemon. return; } } const IBusDaemonStatus on_exit_state = ibus_daemon_status_; ibus_daemon_status_ = IBUS_DAEMON_STOP; FOR_EACH_OBSERVER(Observer, observers_, OnDisconnected()); if (on_exit_state == IBUS_DAEMON_SHUTTING_DOWN) return; // Normal exitting, so do nothing. LOG(ERROR) << "The ibus-daemon crashed. Re-launching..."; StartIBusDaemon(); } // The current ibus_daemon address. This value is assigned at the launching // ibus-daemon and used in bus connection initialization. std::string ibus_daemon_address_; // The process handle of the IBus daemon. kNullProcessHandle if it's not // running. base::ProcessHandle process_handle_; // Represents ibus-daemon's status. IBusDaemonStatus ibus_daemon_status_; // The task runner of UI thread. scoped_refptr ui_task_runner_; // The task runner of FILE thread. scoped_refptr file_task_runner_; ObserverList observers_; base::ThreadChecker thread_checker_; // Used for making callbacks for PostTask. base::WeakPtrFactory weak_ptr_factory_; DISALLOW_COPY_AND_ASSIGN(IBusDaemonControllerImpl); }; // An implementation of IBusDaemonController without ibus-daemon interaction. // Currently this class is used only on linux desktop. // TODO(nona): Remove IBusDaemonControlelr this once crbug.com/171351 is fixed. class IBusDaemonControllerDaemonlessImpl : public IBusDaemonController { public: IBusDaemonControllerDaemonlessImpl() : is_started_(false) {} virtual ~IBusDaemonControllerDaemonlessImpl() {} // IBusDaemonController overrides: virtual void AddObserver(Observer* observer) OVERRIDE { observers_.AddObserver(observer); } virtual void RemoveObserver(Observer* observer) OVERRIDE { observers_.RemoveObserver(observer); } virtual bool Start() OVERRIDE { if (is_started_) return false; // IBusBus should be initialized but it is okay to pass "dummy address" as // the bus address because the actual dbus implementation is stub if the // Chrome OS is working on Linux desktop. This path is not used in // production at this moment, only for Chrome OS on Linux Desktop. // TODO(nona): Remove InitIBusBus oncer all legacy ime is migrated to IME // extension API. DBusThreadManager::Get()->InitIBusBus("dummy address", base::Bind(&base::DoNothing)); is_started_ = true; FOR_EACH_OBSERVER(Observer, observers_, OnConnected()); return true; } virtual bool Stop() OVERRIDE { if (!is_started_) return false; is_started_ = false; FOR_EACH_OBSERVER(Observer, observers_, OnDisconnected()); return true; } private: ObserverList observers_; bool is_started_; DISALLOW_COPY_AND_ASSIGN(IBusDaemonControllerDaemonlessImpl); }; } // namespace /////////////////////////////////////////////////////////////////////////////// // IBusDaemonController IBusDaemonController::IBusDaemonController() { } IBusDaemonController::~IBusDaemonController() { } // static void IBusDaemonController::Initialize( const scoped_refptr& ui_task_runner, const scoped_refptr& file_task_runner) { DCHECK(g_ibus_daemon_controller == NULL) << "Do not call Initialize function multiple times."; g_ibus_daemon_controller = new IBusDaemonControllerDaemonlessImpl(); } // static void IBusDaemonController::InitializeForTesting( IBusDaemonController* controller) { DCHECK(g_ibus_daemon_controller == NULL); DCHECK(controller); g_ibus_daemon_controller = controller; } // static void IBusDaemonController::Shutdown() { delete g_ibus_daemon_controller; g_ibus_daemon_controller = NULL; } // static IBusDaemonController* IBusDaemonController::GetInstance() { return g_ibus_daemon_controller; } } // namespace chromeos