blob: ec799044e07196110c2d7c27b55ae39018663e64 [file] [log] [blame]
// 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<base::SequencedTaskRunner> 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<base::SequencedTaskRunner>& 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<base::SequencedTaskRunner>& ui_task_runner,
const scoped_refptr<base::SequencedTaskRunner>& 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<int>::max()));
scoped_ptr<base::Environment> 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<std::string> 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<base::SequencedTaskRunner> ui_task_runner_;
// The task runner of FILE thread.
scoped_refptr<base::SequencedTaskRunner> file_task_runner_;
ObserverList<Observer> observers_;
base::ThreadChecker thread_checker_;
// Used for making callbacks for PostTask.
base::WeakPtrFactory<IBusDaemonControllerImpl> 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<Observer> observers_;
bool is_started_;
DISALLOW_COPY_AND_ASSIGN(IBusDaemonControllerDaemonlessImpl);
};
} // namespace
///////////////////////////////////////////////////////////////////////////////
// IBusDaemonController
IBusDaemonController::IBusDaemonController() {
}
IBusDaemonController::~IBusDaemonController() {
}
// static
void IBusDaemonController::Initialize(
const scoped_refptr<base::SequencedTaskRunner>& ui_task_runner,
const scoped_refptr<base::SequencedTaskRunner>& 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