Home | History | Annotate | Download | only in ime
      1 // Copyright (c) 2012 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.
      4 
      5 #include "chromeos/ime/ibus_daemon_controller.h"
      6 
      7 #include "base/bind.h"
      8 #include "base/chromeos/chromeos_version.h"
      9 #include "base/environment.h"
     10 #include "base/files/file_path_watcher.h"
     11 #include "base/location.h"
     12 #include "base/logging.h"
     13 #include "base/observer_list.h"
     14 #include "base/process/launch.h"
     15 #include "base/process/process_handle.h"
     16 #include "base/rand_util.h"
     17 #include "base/strings/string_util.h"
     18 #include "base/strings/stringprintf.h"
     19 #include "base/threading/thread_checker.h"
     20 #include "chromeos/dbus/dbus_thread_manager.h"
     21 
     22 namespace chromeos {
     23 
     24 namespace {
     25 
     26 IBusDaemonController* g_ibus_daemon_controller = NULL;
     27 base::FilePathWatcher* g_file_path_watcher = NULL;
     28 
     29 // Called when the ibus-daemon address file is modified.
     30 static void OnFilePathChanged(
     31     const scoped_refptr<base::SequencedTaskRunner> ui_task_runner,
     32     const base::Closure& closure,
     33     const base::FilePath& file_path,
     34     bool failed) {
     35   if (failed)
     36     return;  // Can't recover, do nothing.
     37   if (!g_file_path_watcher)
     38     return;  // Already discarded watch task.
     39 
     40   ui_task_runner->PostTask(FROM_HERE, closure);
     41   ui_task_runner->DeleteSoon(FROM_HERE, g_file_path_watcher);
     42   g_file_path_watcher = NULL;
     43 }
     44 
     45 // Start watching |address_file_path|. If the target file is changed, |callback|
     46 // is called on UI thread. This function should be called on FILE thread.
     47 void StartWatch(
     48     const std::string& address_file_path,
     49     const base::Closure& closure,
     50     const scoped_refptr<base::SequencedTaskRunner>& ui_task_runner) {
     51   // Before start watching, discard on-going watching task.
     52   delete g_file_path_watcher;
     53   g_file_path_watcher = new base::FilePathWatcher;
     54   bool result = g_file_path_watcher->Watch(
     55       base::FilePath::FromUTF8Unsafe(address_file_path),
     56       false,  // do not watch child directory.
     57       base::Bind(&OnFilePathChanged,
     58                  ui_task_runner,
     59                  closure));
     60   DCHECK(result);
     61 }
     62 
     63 // The implementation of IBusDaemonController.
     64 class IBusDaemonControllerImpl : public IBusDaemonController {
     65  public:
     66   // Represents current ibus-daemon status.
     67   enum IBusDaemonStatus {
     68     IBUS_DAEMON_INITIALIZING,
     69     IBUS_DAEMON_RUNNING,
     70     IBUS_DAEMON_SHUTTING_DOWN,
     71     IBUS_DAEMON_STOP,
     72   };
     73 
     74   IBusDaemonControllerImpl(
     75       const scoped_refptr<base::SequencedTaskRunner>& ui_task_runner,
     76       const scoped_refptr<base::SequencedTaskRunner>& file_task_runner)
     77       : process_handle_(base::kNullProcessHandle),
     78       ibus_daemon_status_(IBUS_DAEMON_STOP),
     79       ui_task_runner_(ui_task_runner),
     80       file_task_runner_(file_task_runner),
     81       weak_ptr_factory_(this) {
     82   }
     83 
     84   virtual ~IBusDaemonControllerImpl() {}
     85 
     86   // IBusDaemonController override:
     87   virtual void AddObserver(Observer* observer) OVERRIDE {
     88     DCHECK(thread_checker_.CalledOnValidThread());
     89     observers_.AddObserver(observer);
     90   }
     91 
     92   // IBusDaemonController override:
     93   virtual void RemoveObserver(Observer* observer) OVERRIDE {
     94     DCHECK(thread_checker_.CalledOnValidThread());
     95     observers_.RemoveObserver(observer);
     96   }
     97 
     98   // IBusDaemonController override:
     99   virtual bool Start() OVERRIDE {
    100     DCHECK(thread_checker_.CalledOnValidThread());
    101     if (ibus_daemon_status_ == IBUS_DAEMON_RUNNING)
    102       return true;
    103     if (ibus_daemon_status_ == IBUS_DAEMON_STOP ||
    104         ibus_daemon_status_ == IBUS_DAEMON_SHUTTING_DOWN) {
    105       return StartIBusDaemon();
    106     }
    107     return true;
    108   }
    109 
    110   // IBusDaemonController override:
    111   virtual bool Stop() OVERRIDE {
    112     DCHECK(thread_checker_.CalledOnValidThread());
    113     NOTREACHED() << "Termination of ibus-daemon is not supported"
    114                  << "http://crosbug.com/27051";
    115     return false;
    116   }
    117 
    118  private:
    119   // Starts ibus-daemon service.
    120   bool StartIBusDaemon() {
    121     if (ibus_daemon_status_ == IBUS_DAEMON_INITIALIZING ||
    122         ibus_daemon_status_ == IBUS_DAEMON_RUNNING) {
    123       DVLOG(1) << "MaybeLaunchIBusDaemon: ibus-daemon is already running.";
    124       return false;
    125     }
    126 
    127     ibus_daemon_status_ = IBUS_DAEMON_INITIALIZING;
    128     ibus_daemon_address_ = base::StringPrintf(
    129         "unix:abstract=ibus-%d",
    130         base::RandInt(0, std::numeric_limits<int>::max()));
    131 
    132     scoped_ptr<base::Environment> env(base::Environment::Create());
    133     std::string address_file_path;
    134     env->GetVar("IBUS_ADDRESS_FILE", &address_file_path);
    135     DCHECK(!address_file_path.empty());
    136 
    137     // Set up ibus-daemon address file watcher before launching ibus-daemon,
    138     // because if watcher starts after ibus-daemon, we may miss the ibus
    139     // connection initialization.
    140     bool success = file_task_runner_->PostTaskAndReply(
    141         FROM_HERE,
    142         base::Bind(&StartWatch,
    143                    address_file_path,
    144                    base::Bind(&IBusDaemonControllerImpl::FilePathChanged,
    145                               weak_ptr_factory_.GetWeakPtr(),
    146                               ibus_daemon_address_),
    147                    ui_task_runner_),
    148         base::Bind(&IBusDaemonControllerImpl::LaunchIBusDaemon,
    149                    weak_ptr_factory_.GetWeakPtr(),
    150                    ibus_daemon_address_));
    151     DCHECK(success);
    152     return true;
    153   }
    154 
    155   // Launhes actual ibus-daemon process.
    156   void LaunchIBusDaemon(const std::string& ibus_address) {
    157     DCHECK(thread_checker_.CalledOnValidThread());
    158     DCHECK_EQ(base::kNullProcessHandle, process_handle_);
    159     static const char kIBusDaemonPath[] = "/usr/bin/ibus-daemon";
    160     // TODO(zork): Send output to /var/log/ibus.log
    161     std::vector<std::string> ibus_daemon_command_line;
    162     ibus_daemon_command_line.push_back(kIBusDaemonPath);
    163     ibus_daemon_command_line.push_back("--panel=disable");
    164     ibus_daemon_command_line.push_back("--cache=none");
    165     ibus_daemon_command_line.push_back("--restart");
    166     ibus_daemon_command_line.push_back("--replace");
    167     ibus_daemon_command_line.push_back("--address=" + ibus_address);
    168 
    169     if (!base::LaunchProcess(ibus_daemon_command_line,
    170                              base::LaunchOptions(),
    171                              &process_handle_)) {
    172       LOG(WARNING) << "Could not launch: "
    173                    << JoinString(ibus_daemon_command_line, " ");
    174     }
    175   }
    176 
    177   // Called by FilePathWatcher when the ibus-daemon address file is changed.
    178   // This function will be called on FILE thread.
    179   void FilePathChanged(const std::string& ibus_address) {
    180     ui_task_runner_->PostTask(
    181         FROM_HERE,
    182         base::Bind(&IBusDaemonControllerImpl::IBusDaemonInitializationDone,
    183                    weak_ptr_factory_.GetWeakPtr(),
    184                    ibus_address));
    185   }
    186 
    187   // Called by FilePathChaged function, this function should be called on UI
    188   // thread.
    189   void IBusDaemonInitializationDone(const std::string& ibus_address) {
    190     if (ibus_daemon_address_ != ibus_address)
    191       return;
    192 
    193     if (ibus_daemon_status_ != IBUS_DAEMON_INITIALIZING) {
    194       // Stop() or OnIBusDaemonExit() has already been called.
    195       return;
    196     }
    197 
    198     DBusThreadManager::Get()->InitIBusBus(
    199         ibus_address,
    200         base::Bind(&IBusDaemonControllerImpl::OnIBusDaemonDisconnected,
    201                    weak_ptr_factory_.GetWeakPtr(),
    202                    base::GetProcId(process_handle_)));
    203     ibus_daemon_status_ = IBUS_DAEMON_RUNNING;
    204     FOR_EACH_OBSERVER(Observer, observers_, OnConnected());
    205 
    206     VLOG(1) << "The ibus-daemon initialization is done.";
    207   }
    208 
    209   // Called when the connection with ibus-daemon is disconnected.
    210   void OnIBusDaemonDisconnected(base::ProcessId pid) {
    211     if (!chromeos::DBusThreadManager::Get())
    212       return;  // Expected disconnection at shutting down. do nothing.
    213 
    214     if (process_handle_ != base::kNullProcessHandle) {
    215       if (base::GetProcId(process_handle_) == pid) {
    216         // ibus-daemon crashed.
    217         // TODO(nona): Shutdown ibus-bus connection.
    218         process_handle_ = base::kNullProcessHandle;
    219       } else {
    220         // This condition is as follows.
    221         // 1. Called Stop (process_handle_ becomes null)
    222         // 2. Called LaunchProcess (process_handle_ becomes new instance)
    223         // 3. Callbacked OnIBusDaemonExit for old instance and reach here.
    224         // In this case, we should not reset process_handle_ as null, and do not
    225         // re-launch ibus-daemon.
    226         return;
    227       }
    228     }
    229 
    230     const IBusDaemonStatus on_exit_state = ibus_daemon_status_;
    231     ibus_daemon_status_ = IBUS_DAEMON_STOP;
    232     FOR_EACH_OBSERVER(Observer, observers_, OnDisconnected());
    233 
    234     if (on_exit_state == IBUS_DAEMON_SHUTTING_DOWN)
    235       return;  // Normal exitting, so do nothing.
    236 
    237     LOG(ERROR) << "The ibus-daemon crashed. Re-launching...";
    238     StartIBusDaemon();
    239   }
    240 
    241   // The current ibus_daemon address. This value is assigned at the launching
    242   // ibus-daemon and used in bus connection initialization.
    243   std::string ibus_daemon_address_;
    244 
    245   // The process handle of the IBus daemon. kNullProcessHandle if it's not
    246   // running.
    247   base::ProcessHandle process_handle_;
    248 
    249   // Represents ibus-daemon's status.
    250   IBusDaemonStatus ibus_daemon_status_;
    251 
    252   // The task runner of UI thread.
    253   scoped_refptr<base::SequencedTaskRunner> ui_task_runner_;
    254 
    255   // The task runner of FILE thread.
    256   scoped_refptr<base::SequencedTaskRunner> file_task_runner_;
    257 
    258   ObserverList<Observer> observers_;
    259   base::ThreadChecker thread_checker_;
    260 
    261   // Used for making callbacks for PostTask.
    262   base::WeakPtrFactory<IBusDaemonControllerImpl> weak_ptr_factory_;
    263 
    264   DISALLOW_COPY_AND_ASSIGN(IBusDaemonControllerImpl);
    265 };
    266 
    267 // An implementation of IBusDaemonController without ibus-daemon interaction.
    268 // Currently this class is used only on linux desktop.
    269 // TODO(nona): Remove IBusDaemonControlelr this once crbug.com/171351 is fixed.
    270 class IBusDaemonControllerDaemonlessImpl : public IBusDaemonController {
    271  public:
    272   IBusDaemonControllerDaemonlessImpl()
    273       : is_started_(false) {}
    274   virtual ~IBusDaemonControllerDaemonlessImpl() {}
    275 
    276   // IBusDaemonController overrides:
    277   virtual void AddObserver(Observer* observer) OVERRIDE {
    278     observers_.AddObserver(observer);
    279   }
    280 
    281   virtual void RemoveObserver(Observer* observer) OVERRIDE {
    282     observers_.RemoveObserver(observer);
    283   }
    284 
    285   virtual bool Start() OVERRIDE {
    286     if (is_started_)
    287       return false;
    288     // IBusBus should be initialized but it is okay to pass "dummy address" as
    289     // the bus address because the actual dbus implementation is stub if the
    290     // Chrome OS is working on Linux desktop. This path is not used in
    291     // production at this moment, only for Chrome OS on Linux Desktop.
    292     // TODO(nona): Remove InitIBusBus oncer all legacy ime is migrated to IME
    293     // extension API.
    294     DCHECK(!base::chromeos::IsRunningOnChromeOS());
    295     DBusThreadManager::Get()->InitIBusBus("dummy address",
    296                                           base::Bind(&base::DoNothing));
    297     is_started_ = true;
    298     FOR_EACH_OBSERVER(Observer, observers_, OnConnected());
    299     return true;
    300   }
    301   virtual bool Stop() OVERRIDE {
    302     if (!is_started_)
    303       return false;
    304     is_started_ = false;
    305     FOR_EACH_OBSERVER(Observer, observers_, OnDisconnected());
    306     return true;
    307   }
    308 
    309  private:
    310   ObserverList<Observer> observers_;
    311   bool is_started_;
    312   DISALLOW_COPY_AND_ASSIGN(IBusDaemonControllerDaemonlessImpl);
    313 };
    314 
    315 }  // namespace
    316 
    317 ///////////////////////////////////////////////////////////////////////////////
    318 // IBusDaemonController
    319 
    320 IBusDaemonController::IBusDaemonController() {
    321 }
    322 
    323 IBusDaemonController::~IBusDaemonController() {
    324 }
    325 
    326 // static
    327 void IBusDaemonController::Initialize(
    328     const scoped_refptr<base::SequencedTaskRunner>& ui_task_runner,
    329     const scoped_refptr<base::SequencedTaskRunner>& file_task_runner) {
    330   DCHECK(g_ibus_daemon_controller == NULL)
    331       << "Do not call Initialize function multiple times.";
    332   if (base::chromeos::IsRunningOnChromeOS()) {
    333     g_ibus_daemon_controller = new IBusDaemonControllerImpl(ui_task_runner,
    334                                                             file_task_runner);
    335   } else {
    336     g_ibus_daemon_controller = new IBusDaemonControllerDaemonlessImpl();
    337   }
    338 }
    339 
    340 // static
    341 void IBusDaemonController::InitializeForTesting(
    342     IBusDaemonController* controller) {
    343   DCHECK(g_ibus_daemon_controller == NULL);
    344   DCHECK(controller);
    345   g_ibus_daemon_controller = controller;
    346 }
    347 
    348 // static
    349 void IBusDaemonController::Shutdown() {
    350   delete g_ibus_daemon_controller;
    351   g_ibus_daemon_controller = NULL;
    352 }
    353 
    354 // static
    355 IBusDaemonController* IBusDaemonController::GetInstance() {
    356   return g_ibus_daemon_controller;
    357 }
    358 
    359 }  // namespace chromeos
    360