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