| OLD | NEW |
| 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/browser/chromeos/cros/input_method_library.h" | 5 #include "chrome/browser/chromeos/cros/input_method_library.h" |
| 6 | 6 |
| 7 #include "base/basictypes.h" | 7 #include "base/basictypes.h" |
| 8 #include "base/message_loop.h" | 8 #include "base/message_loop.h" |
| 9 #include "base/string_util.h" | 9 #include "base/string_util.h" |
| 10 #include "chrome/browser/chrome_thread.h" | 10 #include "chrome/browser/chrome_thread.h" |
| 11 #include "chrome/browser/chromeos/cros/cros_library.h" | 11 #include "chrome/browser/chromeos/cros/cros_library.h" |
| 12 #include "chrome/browser/chromeos/cros/keyboard_library.h" | 12 #include "chrome/browser/chromeos/cros/keyboard_library.h" |
| 13 #include "chrome/browser/chromeos/language_preferences.h" | 13 #include "chrome/browser/chromeos/language_preferences.h" |
| 14 #include "third_party/icu/public/common/unicode/uloc.h" | 14 #include "third_party/icu/public/common/unicode/uloc.h" |
| 15 | 15 |
| 16 #include <glib.h> | 16 #include <glib.h> |
| 17 #include <signal.h> | 17 #include <signal.h> |
| 18 | 18 |
| 19 // Allows InvokeLater without adding refcounting. This class is a Singleton and | |
| 20 // won't be deleted until it's last InvokeLater is run. | |
| 21 DISABLE_RUNNABLE_METHOD_REFCOUNT(chromeos::InputMethodLibraryImpl); | |
| 22 | |
| 23 namespace { | 19 namespace { |
| 24 | 20 |
| 25 // Finds a property which has |new_prop.key| from |prop_list|, and replaces the | 21 // Finds a property which has |new_prop.key| from |prop_list|, and replaces the |
| 26 // property with |new_prop|. Returns true if such a property is found. | 22 // property with |new_prop|. Returns true if such a property is found. |
| 27 bool FindAndUpdateProperty(const chromeos::ImeProperty& new_prop, | 23 bool FindAndUpdateProperty(const chromeos::ImeProperty& new_prop, |
| 28 chromeos::ImePropertyList* prop_list) { | 24 chromeos::ImePropertyList* prop_list) { |
| 29 for (size_t i = 0; i < prop_list->size(); ++i) { | 25 for (size_t i = 0; i < prop_list->size(); ++i) { |
| 30 chromeos::ImeProperty& prop = prop_list->at(i); | 26 chromeos::ImeProperty& prop = prop_list->at(i); |
| 31 if (prop.key == new_prop.key) { | 27 if (prop.key == new_prop.key) { |
| 32 const int saved_id = prop.selection_item_id; | 28 const int saved_id = prop.selection_item_id; |
| 33 // Update the list except the radio id. As written in | 29 // Update the list except the radio id. As written in |
| 34 // chromeos_input_method.h, |prop.selection_item_id| is dummy. | 30 // chromeos_input_method.h, |prop.selection_item_id| is dummy. |
| 35 prop = new_prop; | 31 prop = new_prop; |
| 36 prop.selection_item_id = saved_id; | 32 prop.selection_item_id = saved_id; |
| 37 return true; | 33 return true; |
| 38 } | 34 } |
| 39 } | 35 } |
| 40 return false; | 36 return false; |
| 41 } | 37 } |
| 42 | 38 |
| 43 // The default keyboard layout. | 39 // The default keyboard layout. |
| 44 const char kDefaultKeyboardLayout[] = "us"; | 40 const char kDefaultKeyboardLayout[] = "us"; |
| 45 | 41 |
| 46 } // namespace | 42 } // namespace |
| 47 | 43 |
| 48 namespace chromeos { | 44 namespace chromeos { |
| 49 | 45 |
| 50 InputMethodLibraryImpl::InputMethodLibraryImpl() | 46 // This class handles the interaction with the ChromeOS language library APIs. |
| 51 : input_method_status_connection_(NULL), | 47 // Classes can add themselves as observers. Users can get an instance of this |
| 52 previous_input_method_("", "", "", ""), | 48 // library class like this: InputMethodLibrary::Get() |
| 53 current_input_method_("", "", "", ""), | 49 class InputMethodLibraryImpl : public InputMethodLibrary { |
| 54 ime_running_(false), | 50 public: |
| 55 ime_connected_(false), | 51 InputMethodLibraryImpl() |
| 56 defer_ime_startup_(false), | 52 : input_method_status_connection_(NULL), |
| 57 active_input_method_(kHardwareKeyboardLayout), | 53 previous_input_method_("", "", "", ""), |
| 58 need_input_method_set_(false), | 54 current_input_method_("", "", "", ""), |
| 59 ime_handle_(0), | 55 ime_running_(false), |
| 60 candidate_window_handle_(0) { | 56 ime_connected_(false), |
| 61 scoped_ptr<InputMethodDescriptors> input_method_descriptors( | 57 defer_ime_startup_(false), |
| 62 CreateFallbackInputMethodDescriptors()); | 58 active_input_method_(kHardwareKeyboardLayout), |
| 63 current_input_method_ = input_method_descriptors->at(0); | 59 need_input_method_set_(false), |
| 60 ime_handle_(0), |
| 61 candidate_window_handle_(0) { |
| 62 scoped_ptr<InputMethodDescriptors> input_method_descriptors( |
| 63 CreateFallbackInputMethodDescriptors()); |
| 64 current_input_method_ = input_method_descriptors->at(0); |
| 65 } |
| 66 |
| 67 ~InputMethodLibraryImpl() { |
| 68 StopInputMethodProcesses(); |
| 69 } |
| 70 |
| 71 void AddObserver(Observer* observer) { |
| 72 observers_.AddObserver(observer); |
| 73 } |
| 74 |
| 75 void RemoveObserver(Observer* observer) { |
| 76 observers_.RemoveObserver(observer); |
| 77 } |
| 78 |
| 79 InputMethodDescriptors* GetActiveInputMethods() { |
| 80 chromeos::InputMethodDescriptors* result = NULL; |
| 81 // The connection does not need to be alive, but it does need to be created. |
| 82 if (EnsureLoadedAndStarted()) { |
| 83 result = chromeos::GetActiveInputMethods(input_method_status_connection_); |
| 84 } |
| 85 if (!result || result->empty()) { |
| 86 result = CreateFallbackInputMethodDescriptors(); |
| 87 } |
| 88 return result; |
| 89 } |
| 90 |
| 91 size_t GetNumActiveInputMethods() { |
| 92 scoped_ptr<InputMethodDescriptors> input_methods(GetActiveInputMethods()); |
| 93 return input_methods->size(); |
| 94 } |
| 95 |
| 96 InputMethodDescriptors* GetSupportedInputMethods() { |
| 97 InputMethodDescriptors* result = NULL; |
| 98 // The connection does not need to be alive, but it does need to be created. |
| 99 if (EnsureLoadedAndStarted()) { |
| 100 result = chromeos::GetSupportedInputMethods( |
| 101 input_method_status_connection_); |
| 102 } |
| 103 if (!result || result->empty()) { |
| 104 result = CreateFallbackInputMethodDescriptors(); |
| 105 } |
| 106 return result; |
| 107 } |
| 108 |
| 109 void ChangeInputMethod(const std::string& input_method_id) { |
| 110 if (EnsureLoadedAndStarted()) { |
| 111 if (input_method_id != kHardwareKeyboardLayout) { |
| 112 StartInputMethodProcesses(); |
| 113 } |
| 114 chromeos::ChangeInputMethod( |
| 115 input_method_status_connection_, input_method_id.c_str()); |
| 116 } |
| 117 } |
| 118 |
| 119 void SetImePropertyActivated(const std::string& key, |
| 120 bool activated) { |
| 121 DCHECK(!key.empty()); |
| 122 if (EnsureLoadedAndStarted()) { |
| 123 chromeos::SetImePropertyActivated( |
| 124 input_method_status_connection_, key.c_str(), activated); |
| 125 } |
| 126 } |
| 127 |
| 128 bool InputMethodIsActivated( |
| 129 const std::string& input_method_id) { |
| 130 scoped_ptr<InputMethodDescriptors> active_input_method_descriptors( |
| 131 CrosLibrary::Get()->GetInputMethodLibrary()->GetActiveInputMethods()); |
| 132 for (size_t i = 0; i < active_input_method_descriptors->size(); ++i) { |
| 133 if (active_input_method_descriptors->at(i).id == input_method_id) { |
| 134 return true; |
| 135 } |
| 136 } |
| 137 return false; |
| 138 } |
| 139 |
| 140 bool GetImeConfig( |
| 141 const char* section, |
| 142 const char* config_name, |
| 143 ImeConfigValue* out_value) { |
| 144 bool success = false; |
| 145 if (EnsureLoadedAndStarted()) { |
| 146 success = chromeos::GetImeConfig( |
| 147 input_method_status_connection_, section, config_name, out_value); |
| 148 } |
| 149 return success; |
| 150 } |
| 151 |
| 152 bool SetImeConfig( |
| 153 const char* section, |
| 154 const char* config_name, |
| 155 const ImeConfigValue& value) { |
| 156 MaybeUpdateImeState(section, config_name, value); |
| 157 |
| 158 const ConfigKeyType key = std::make_pair(section, config_name); |
| 159 current_config_values_[key] = value; |
| 160 if (ime_connected_) { |
| 161 pending_config_requests_[key] = value; |
| 162 FlushImeConfig(); |
| 163 } |
| 164 return pending_config_requests_.empty(); |
| 165 } |
| 166 |
| 167 virtual const InputMethodDescriptor& previous_input_method() const { |
| 168 return previous_input_method_; |
| 169 } |
| 170 virtual const InputMethodDescriptor& current_input_method() const { |
| 171 return current_input_method_; |
| 172 } |
| 173 |
| 174 virtual const ImePropertyList& current_ime_properties() const { |
| 175 return current_ime_properties_; |
| 176 } |
| 177 |
| 178 void StartIme() { |
| 179 ime_running_ = true; |
| 180 MaybeLaunchIme(); |
| 181 } |
| 182 |
| 183 void StopIme() { |
| 184 ime_running_ = false; |
| 185 if (ime_handle_) { |
| 186 kill(ime_handle_, SIGTERM); |
| 187 ime_handle_ = 0; |
| 188 } |
| 189 if (candidate_window_handle_) { |
| 190 kill(candidate_window_handle_, SIGTERM); |
| 191 candidate_window_handle_ = 0; |
| 192 } |
| 193 } |
| 194 |
| 195 private: |
| 196 void MaybeUpdateImeState( |
| 197 const char* section, |
| 198 const char* config_name, |
| 199 const ImeConfigValue& value) { |
| 200 if (!strcmp(kGeneralSectionName, section) && |
| 201 !strcmp(kPreloadEnginesConfigName, config_name)) { |
| 202 if (EnsureLoadedAndStarted()) { |
| 203 if (value.type == ImeConfigValue::kValueTypeStringList && |
| 204 value.string_list_value.size() == 1 && |
| 205 value.string_list_value[0] == kHardwareKeyboardLayout) { |
| 206 StopInputMethodProcesses(); |
| 207 } else if (!defer_ime_startup_) { |
| 208 StartInputMethodProcesses(); |
| 209 } |
| 210 chromeos::SetActiveInputMethods(input_method_status_connection_, value); |
| 211 } |
| 212 } |
| 213 } |
| 214 |
| 215 void FlushImeConfig() { |
| 216 bool active_input_methods_are_changed = false; |
| 217 bool completed = false; |
| 218 if (EnsureLoadedAndStarted()) { |
| 219 InputMethodConfigRequests::iterator iter = |
| 220 pending_config_requests_.begin(); |
| 221 while (iter != pending_config_requests_.end()) { |
| 222 const std::string& section = iter->first.first; |
| 223 const std::string& config_name = iter->first.second; |
| 224 const ImeConfigValue& value = iter->second; |
| 225 if (chromeos::SetImeConfig(input_method_status_connection_, |
| 226 section.c_str(), |
| 227 config_name.c_str(), |
| 228 value)) { |
| 229 // Check if it's a change in active input methods. |
| 230 if (config_name == kPreloadEnginesConfigName) { |
| 231 active_input_methods_are_changed = true; |
| 232 } |
| 233 // Successfully sent. Remove the command and proceed to the next one. |
| 234 pending_config_requests_.erase(iter++); |
| 235 } else { |
| 236 // If SetImeConfig() fails, subsequent calls will likely fail. |
| 237 break; |
| 238 } |
| 239 } |
| 240 if (pending_config_requests_.empty()) { |
| 241 // Calls to ChangeInputMethod() will fail if the input method has not |
| 242 // yet been added to preload_engines. As such, the call is deferred |
| 243 // until after all config values have been sent to the IME process. |
| 244 if (need_input_method_set_) { |
| 245 if (chromeos::ChangeInputMethod(input_method_status_connection_, |
| 246 active_input_method_.c_str())) { |
| 247 need_input_method_set_ = false; |
| 248 completed = true; |
| 249 active_input_methods_are_changed = true; |
| 250 } |
| 251 } else { |
| 252 completed = true; |
| 253 } |
| 254 } |
| 255 } |
| 256 |
| 257 if (completed) { |
| 258 timer_.Stop(); // no-op if it's not running. |
| 259 } else { |
| 260 if (!timer_.IsRunning()) { |
| 261 static const int64 kTimerIntervalInMsec = 100; |
| 262 timer_.Start(base::TimeDelta::FromMilliseconds(kTimerIntervalInMsec), |
| 263 this, &InputMethodLibraryImpl::FlushImeConfig); |
| 264 } |
| 265 } |
| 266 if (active_input_methods_are_changed) { |
| 267 FOR_EACH_OBSERVER(Observer, observers_, ActiveInputMethodsChanged(this)); |
| 268 } |
| 269 } |
| 270 |
| 271 static void InputMethodChangedHandler( |
| 272 void* object, |
| 273 const chromeos::InputMethodDescriptor& current_input_method) { |
| 274 InputMethodLibraryImpl* input_method_library = |
| 275 static_cast<InputMethodLibraryImpl*>(object); |
| 276 input_method_library->UpdateCurrentInputMethod(current_input_method); |
| 277 } |
| 278 |
| 279 static void RegisterPropertiesHandler( |
| 280 void* object, const ImePropertyList& prop_list) { |
| 281 InputMethodLibraryImpl* input_method_library = |
| 282 static_cast<InputMethodLibraryImpl*>(object); |
| 283 input_method_library->RegisterProperties(prop_list); |
| 284 } |
| 285 |
| 286 static void UpdatePropertyHandler( |
| 287 void* object, const ImePropertyList& prop_list) { |
| 288 InputMethodLibraryImpl* input_method_library = |
| 289 static_cast<InputMethodLibraryImpl*>(object); |
| 290 input_method_library->UpdateProperty(prop_list); |
| 291 } |
| 292 |
| 293 static void ConnectionChangeHandler(void* object, bool connected) { |
| 294 InputMethodLibraryImpl* input_method_library = |
| 295 static_cast<InputMethodLibraryImpl*>(object); |
| 296 input_method_library->ime_connected_ = connected; |
| 297 if (connected) { |
| 298 input_method_library->pending_config_requests_.clear(); |
| 299 input_method_library->pending_config_requests_.insert( |
| 300 input_method_library->current_config_values_.begin(), |
| 301 input_method_library->current_config_values_.end()); |
| 302 // When the IME process starts up, the hardware layout will be the current |
| 303 // method. If this is not correct, we'll need to explicitly change it. |
| 304 if (input_method_library->active_input_method_ != |
| 305 kHardwareKeyboardLayout) { |
| 306 input_method_library->need_input_method_set_ = true; |
| 307 } |
| 308 input_method_library->FlushImeConfig(); |
| 309 } else { |
| 310 // Stop attempting to resend config data, since it will continue to fail. |
| 311 input_method_library->timer_.Stop(); // no-op if it's not running. |
| 312 } |
| 313 } |
| 314 |
| 315 bool EnsureStarted() { |
| 316 if (!input_method_status_connection_) { |
| 317 input_method_status_connection_ = chromeos::MonitorInputMethodStatus( |
| 318 this, |
| 319 &InputMethodChangedHandler, |
| 320 &RegisterPropertiesHandler, |
| 321 &UpdatePropertyHandler, |
| 322 &ConnectionChangeHandler); |
| 323 } |
| 324 return true; |
| 325 } |
| 326 |
| 327 bool EnsureLoadedAndStarted() { |
| 328 return CrosLibrary::Get()->EnsureLoaded() && |
| 329 EnsureStarted(); |
| 330 } |
| 331 |
| 332 void UpdateCurrentInputMethod(const InputMethodDescriptor& new_input_method) { |
| 333 // Make sure we run on UI thread. |
| 334 if (!ChromeThread::CurrentlyOn(ChromeThread::UI)) { |
| 335 DLOG(INFO) << "UpdateCurrentInputMethod (Background thread)"; |
| 336 ChromeThread::PostTask( |
| 337 ChromeThread::UI, FROM_HERE, |
| 338 // NewRunnableMethod() copies |new_input_method| by value. |
| 339 NewRunnableMethod( |
| 340 this, &InputMethodLibraryImpl::UpdateCurrentInputMethod, |
| 341 new_input_method)); |
| 342 return; |
| 343 } |
| 344 |
| 345 DLOG(INFO) << "UpdateCurrentInputMethod (UI thread)"; |
| 346 // Change the keyboard layout to a preferred layout for the input method. |
| 347 CrosLibrary::Get()->GetKeyboardLibrary()->SetCurrentKeyboardLayoutByName( |
| 348 new_input_method.keyboard_layout); |
| 349 |
| 350 if (current_input_method_.id != new_input_method.id) { |
| 351 previous_input_method_ = current_input_method_; |
| 352 current_input_method_ = new_input_method; |
| 353 } |
| 354 FOR_EACH_OBSERVER(Observer, observers_, InputMethodChanged(this)); |
| 355 } |
| 356 |
| 357 void RegisterProperties(const ImePropertyList& prop_list) { |
| 358 if (!ChromeThread::CurrentlyOn(ChromeThread::UI)) { |
| 359 ChromeThread::PostTask( |
| 360 ChromeThread::UI, FROM_HERE, |
| 361 NewRunnableMethod( |
| 362 this, &InputMethodLibraryImpl::RegisterProperties, prop_list)); |
| 363 return; |
| 364 } |
| 365 |
| 366 // |prop_list| might be empty. This means "clear all properties." |
| 367 current_ime_properties_ = prop_list; |
| 368 FOR_EACH_OBSERVER(Observer, observers_, ImePropertiesChanged(this)); |
| 369 } |
| 370 |
| 371 void StartInputMethodProcesses() { |
| 372 ime_running_ = true; |
| 373 MaybeLaunchIme(); |
| 374 } |
| 375 |
| 376 void UpdateProperty(const ImePropertyList& prop_list) { |
| 377 if (!ChromeThread::CurrentlyOn(ChromeThread::UI)) { |
| 378 ChromeThread::PostTask( |
| 379 ChromeThread::UI, FROM_HERE, |
| 380 NewRunnableMethod( |
| 381 this, &InputMethodLibraryImpl::UpdateProperty, prop_list)); |
| 382 return; |
| 383 } |
| 384 |
| 385 for (size_t i = 0; i < prop_list.size(); ++i) { |
| 386 FindAndUpdateProperty(prop_list[i], ¤t_ime_properties_); |
| 387 } |
| 388 FOR_EACH_OBSERVER(Observer, observers_, ImePropertiesChanged(this)); |
| 389 } |
| 390 |
| 391 void MaybeLaunchIme() { |
| 392 if (!ime_running_) { |
| 393 return; |
| 394 } |
| 395 |
| 396 // TODO(zork): export "LD_PRELOAD=/usr/lib/libcrash.so" |
| 397 GSpawnFlags flags = G_SPAWN_DO_NOT_REAP_CHILD; |
| 398 if (ime_handle_ == 0) { |
| 399 GError *error = NULL; |
| 400 gchar **argv; |
| 401 gint argc; |
| 402 |
| 403 if (!g_shell_parse_argv( |
| 404 "/usr/bin/ibus-daemon --panel=disable --cache=none --restart", |
| 405 &argc, &argv, &error)) { |
| 406 LOG(ERROR) << "Could not parse command: " << error->message; |
| 407 g_error_free(error); |
| 408 return; |
| 409 } |
| 410 |
| 411 error = NULL; |
| 412 int handle; |
| 413 // TODO(zork): Send output to /var/log/ibus.log |
| 414 gboolean result = g_spawn_async(NULL, argv, NULL, |
| 415 flags, NULL, NULL, |
| 416 &handle, &error); |
| 417 g_strfreev(argv); |
| 418 if (!result) { |
| 419 LOG(ERROR) << "Could not launch ime: " << error->message; |
| 420 g_error_free(error); |
| 421 return; |
| 422 } |
| 423 ime_handle_ = handle; |
| 424 g_child_watch_add(ime_handle_, (GChildWatchFunc)OnImeShutdown, this); |
| 425 } |
| 426 |
| 427 if (candidate_window_handle_ == 0) { |
| 428 GError *error = NULL; |
| 429 gchar **argv; |
| 430 gint argc; |
| 431 |
| 432 if (!g_shell_parse_argv("/opt/google/chrome/candidate_window", |
| 433 &argc, &argv, &error)) { |
| 434 LOG(ERROR) << "Could not parse command: " << error->message; |
| 435 g_error_free(error); |
| 436 return; |
| 437 } |
| 438 |
| 439 error = NULL; |
| 440 int handle; |
| 441 gboolean result = g_spawn_async(NULL, argv, NULL, |
| 442 flags, NULL, NULL, |
| 443 &handle, &error); |
| 444 g_strfreev(argv); |
| 445 if (!result) { |
| 446 LOG(ERROR) << "Could not launch ime candidate window" << error->message; |
| 447 g_error_free(error); |
| 448 return; |
| 449 } |
| 450 candidate_window_handle_ = handle; |
| 451 g_child_watch_add(candidate_window_handle_, |
| 452 (GChildWatchFunc)OnImeShutdown, this); |
| 453 } |
| 454 } |
| 455 |
| 456 static void OnImeShutdown(int pid, |
| 457 int status, |
| 458 InputMethodLibraryImpl* library) { |
| 459 g_spawn_close_pid(pid); |
| 460 if (library->ime_handle_ == pid) { |
| 461 library->ime_handle_ = 0; |
| 462 } else if (library->candidate_window_handle_ == pid) { |
| 463 library->candidate_window_handle_ = 0; |
| 464 } |
| 465 |
| 466 library->MaybeLaunchIme(); |
| 467 } |
| 468 |
| 469 void StopInputMethodProcesses() { |
| 470 ime_running_ = false; |
| 471 if (ime_handle_) { |
| 472 kill(ime_handle_, SIGTERM); |
| 473 ime_handle_ = 0; |
| 474 } |
| 475 } |
| 476 |
| 477 void SetDeferImeStartup(bool defer) { |
| 478 defer_ime_startup_ = defer; |
| 479 } |
| 480 // A reference to the language api, to allow callbacks when the input method |
| 481 // status changes. |
| 482 InputMethodStatusConnection* input_method_status_connection_; |
| 483 ObserverList<Observer> observers_; |
| 484 |
| 485 // The input method which was/is selected. |
| 486 InputMethodDescriptor previous_input_method_; |
| 487 InputMethodDescriptor current_input_method_; |
| 488 |
| 489 // The input method properties which the current input method uses. The list |
| 490 // might be empty when no input method is used. |
| 491 ImePropertyList current_ime_properties_; |
| 492 |
| 493 typedef std::pair<std::string, std::string> ConfigKeyType; |
| 494 typedef std::map<ConfigKeyType, ImeConfigValue> InputMethodConfigRequests; |
| 495 // SetImeConfig requests that are not yet completed. |
| 496 // Use a map to queue config requests, so we only send the last request for |
| 497 // the same config key (i.e. we'll discard ealier requests for the same |
| 498 // config key). As we discard old requests for the same config key, the order |
| 499 // of requests doesn't matter, so it's safe to use a map. |
| 500 InputMethodConfigRequests pending_config_requests_; |
| 501 |
| 502 // Values that have been set via SetImeConfig(). We keep a copy available to |
| 503 // resend if the ime restarts and loses its state. |
| 504 InputMethodConfigRequests current_config_values_; |
| 505 |
| 506 // A timer for retrying to send |pendning_config_commands_| to the input |
| 507 // method config daemon. |
| 508 base::OneShotTimer<InputMethodLibraryImpl> timer_; |
| 509 |
| 510 bool ime_running_; |
| 511 bool ime_connected_; |
| 512 bool defer_ime_startup_; |
| 513 std::string active_input_method_; |
| 514 bool need_input_method_set_; |
| 515 |
| 516 int ime_handle_; |
| 517 int candidate_window_handle_; |
| 518 |
| 519 DISALLOW_COPY_AND_ASSIGN(InputMethodLibraryImpl); |
| 520 }; |
| 521 |
| 522 InputMethodLibraryImpl::Observer::~Observer() {} |
| 523 |
| 524 class InputMethodLibraryStubImpl : public InputMethodLibrary { |
| 525 public: |
| 526 InputMethodLibraryStubImpl() |
| 527 : previous_input_method_("", "", "", ""), |
| 528 current_input_method_("", "", "", "") { |
| 529 } |
| 530 |
| 531 ~InputMethodLibraryStubImpl() {} |
| 532 void AddObserver(Observer* observer) {} |
| 533 void RemoveObserver(Observer* observer) {} |
| 534 |
| 535 InputMethodDescriptors* GetActiveInputMethods() { |
| 536 return CreateFallbackInputMethodDescriptors(); |
| 537 } |
| 538 |
| 539 |
| 540 size_t GetNumActiveInputMethods() { |
| 541 return CreateFallbackInputMethodDescriptors()->size(); |
| 542 } |
| 543 |
| 544 InputMethodDescriptors* GetSupportedInputMethods() { |
| 545 return CreateFallbackInputMethodDescriptors(); |
| 546 } |
| 547 |
| 548 void ChangeInputMethod(const std::string& input_method_id) {} |
| 549 void SetImePropertyActivated(const std::string& key, bool activated) {} |
| 550 |
| 551 bool InputMethodIsActivated(const std::string& input_method_id) { |
| 552 return true; |
| 553 } |
| 554 |
| 555 bool GetImeConfig(const char* section, |
| 556 const char* config_name, |
| 557 ImeConfigValue* out_value) { |
| 558 return false; |
| 559 } |
| 560 |
| 561 bool SetImeConfig(const char* section, |
| 562 const char* config_name, |
| 563 const ImeConfigValue& value) { |
| 564 return false; |
| 565 } |
| 566 |
| 567 virtual const InputMethodDescriptor& previous_input_method() const { |
| 568 return previous_input_method_; |
| 569 } |
| 570 |
| 571 virtual const InputMethodDescriptor& current_input_method() const { |
| 572 return current_input_method_; |
| 573 } |
| 574 |
| 575 virtual const ImePropertyList& current_ime_properties() const { |
| 576 return current_ime_properties_; |
| 577 } |
| 578 |
| 579 virtual void StartInputMethodProcesses() {} |
| 580 virtual void StopInputMethodProcesses() {} |
| 581 virtual void SetDeferImeStartup(bool defer) {} |
| 582 |
| 583 private: |
| 584 InputMethodDescriptor previous_input_method_; |
| 585 InputMethodDescriptor current_input_method_; |
| 586 ImePropertyList current_ime_properties_; |
| 587 |
| 588 DISALLOW_COPY_AND_ASSIGN(InputMethodLibraryStubImpl); |
| 589 }; |
| 590 |
| 591 // static |
| 592 InputMethodLibrary* InputMethodLibrary::GetImpl(bool stub) { |
| 593 if (stub) |
| 594 return new InputMethodLibraryStubImpl(); |
| 595 else |
| 596 return new InputMethodLibraryImpl(); |
| 64 } | 597 } |
| 65 | 598 |
| 66 InputMethodLibraryImpl::~InputMethodLibraryImpl() { | |
| 67 StopInputMethodProcesses(); | |
| 68 } | |
| 69 | |
| 70 InputMethodLibraryImpl::Observer::~Observer() { | |
| 71 } | |
| 72 | |
| 73 void InputMethodLibraryImpl::AddObserver(Observer* observer) { | |
| 74 observers_.AddObserver(observer); | |
| 75 } | |
| 76 | |
| 77 void InputMethodLibraryImpl::RemoveObserver(Observer* observer) { | |
| 78 observers_.RemoveObserver(observer); | |
| 79 } | |
| 80 | |
| 81 chromeos::InputMethodDescriptors* | |
| 82 InputMethodLibraryImpl::GetActiveInputMethods() { | |
| 83 chromeos::InputMethodDescriptors* result = NULL; | |
| 84 // The connection does not need to be alive, but it does need to be created. | |
| 85 if (EnsureLoadedAndStarted()) { | |
| 86 result = chromeos::GetActiveInputMethods(input_method_status_connection_); | |
| 87 } | |
| 88 if (!result || result->empty()) { | |
| 89 result = CreateFallbackInputMethodDescriptors(); | |
| 90 } | |
| 91 return result; | |
| 92 } | |
| 93 | |
| 94 size_t InputMethodLibraryImpl::GetNumActiveInputMethods() { | |
| 95 scoped_ptr<InputMethodDescriptors> input_methods(GetActiveInputMethods()); | |
| 96 return input_methods->size(); | |
| 97 } | |
| 98 | |
| 99 chromeos::InputMethodDescriptors* | |
| 100 InputMethodLibraryImpl::GetSupportedInputMethods() { | |
| 101 chromeos::InputMethodDescriptors* result = NULL; | |
| 102 // The connection does not need to be alive, but it does need to be created. | |
| 103 if (EnsureLoadedAndStarted()) { | |
| 104 result = chromeos::GetSupportedInputMethods( | |
| 105 input_method_status_connection_); | |
| 106 } | |
| 107 if (!result || result->empty()) { | |
| 108 result = CreateFallbackInputMethodDescriptors(); | |
| 109 } | |
| 110 return result; | |
| 111 } | |
| 112 | |
| 113 void InputMethodLibraryImpl::ChangeInputMethod( | |
| 114 const std::string& input_method_id) { | |
| 115 active_input_method_ = input_method_id; | |
| 116 if (EnsureLoadedAndStarted()) { | |
| 117 if (input_method_id != kHardwareKeyboardLayout) { | |
| 118 StartInputMethodProcesses(); | |
| 119 } | |
| 120 chromeos::ChangeInputMethod( | |
| 121 input_method_status_connection_, input_method_id.c_str()); | |
| 122 } | |
| 123 } | |
| 124 | |
| 125 void InputMethodLibraryImpl::SetImePropertyActivated(const std::string& key, | |
| 126 bool activated) { | |
| 127 DCHECK(!key.empty()); | |
| 128 if (EnsureLoadedAndStarted()) { | |
| 129 chromeos::SetImePropertyActivated( | |
| 130 input_method_status_connection_, key.c_str(), activated); | |
| 131 } | |
| 132 } | |
| 133 | |
| 134 bool InputMethodLibraryImpl::InputMethodIsActivated( | |
| 135 const std::string& input_method_id) { | |
| 136 scoped_ptr<InputMethodDescriptors> active_input_method_descriptors( | |
| 137 CrosLibrary::Get()->GetInputMethodLibrary()->GetActiveInputMethods()); | |
| 138 for (size_t i = 0; i < active_input_method_descriptors->size(); ++i) { | |
| 139 if (active_input_method_descriptors->at(i).id == input_method_id) { | |
| 140 return true; | |
| 141 } | |
| 142 } | |
| 143 return false; | |
| 144 } | |
| 145 | |
| 146 bool InputMethodLibraryImpl::GetImeConfig( | |
| 147 const char* section, const char* config_name, ImeConfigValue* out_value) { | |
| 148 bool success = false; | |
| 149 if (EnsureLoadedAndStarted()) { | |
| 150 success = chromeos::GetImeConfig( | |
| 151 input_method_status_connection_, section, config_name, out_value); | |
| 152 } | |
| 153 return success; | |
| 154 } | |
| 155 | |
| 156 bool InputMethodLibraryImpl::SetImeConfig( | |
| 157 const char* section, const char* config_name, const ImeConfigValue& value) { | |
| 158 MaybeUpdateImeState(section, config_name, value); | |
| 159 | |
| 160 const ConfigKeyType key = std::make_pair(section, config_name); | |
| 161 current_config_values_[key] = value; | |
| 162 if (ime_connected_) { | |
| 163 pending_config_requests_[key] = value; | |
| 164 FlushImeConfig(); | |
| 165 } | |
| 166 return pending_config_requests_.empty(); | |
| 167 } | |
| 168 | |
| 169 void InputMethodLibraryImpl::MaybeUpdateImeState( | |
| 170 const char* section, const char* config_name, const ImeConfigValue& value) { | |
| 171 if (!strcmp(kGeneralSectionName, section) && | |
| 172 !strcmp(kPreloadEnginesConfigName, config_name)) { | |
| 173 if (EnsureLoadedAndStarted()) { | |
| 174 if (value.type == ImeConfigValue::kValueTypeStringList && | |
| 175 value.string_list_value.size() == 1 && | |
| 176 value.string_list_value[0] == kHardwareKeyboardLayout) { | |
| 177 StopInputMethodProcesses(); | |
| 178 } else if (!defer_ime_startup_) { | |
| 179 StartInputMethodProcesses(); | |
| 180 } | |
| 181 chromeos::SetActiveInputMethods(input_method_status_connection_, value); | |
| 182 } | |
| 183 } | |
| 184 } | |
| 185 | |
| 186 void InputMethodLibraryImpl::FlushImeConfig() { | |
| 187 bool active_input_methods_are_changed = false; | |
| 188 bool completed = false; | |
| 189 if (EnsureLoadedAndStarted()) { | |
| 190 InputMethodConfigRequests::iterator iter = pending_config_requests_.begin(); | |
| 191 while (iter != pending_config_requests_.end()) { | |
| 192 const std::string& section = iter->first.first; | |
| 193 const std::string& config_name = iter->first.second; | |
| 194 const ImeConfigValue& value = iter->second; | |
| 195 if (chromeos::SetImeConfig(input_method_status_connection_, | |
| 196 section.c_str(), config_name.c_str(), value)) { | |
| 197 // Check if it's a change in active input methods. | |
| 198 if (config_name == kPreloadEnginesConfigName) { | |
| 199 active_input_methods_are_changed = true; | |
| 200 } | |
| 201 // Successfully sent. Remove the command and proceed to the next one. | |
| 202 pending_config_requests_.erase(iter++); | |
| 203 } else { | |
| 204 // If SetImeConfig() fails, subsequent calls will likely fail. | |
| 205 break; | |
| 206 } | |
| 207 } | |
| 208 if (pending_config_requests_.empty()) { | |
| 209 // Calls to ChangeInputMethod() will fail if the input method has not yet | |
| 210 // been added to preload_engines. As such, the call is deferred until | |
| 211 // after all config values have been sent to the IME process. | |
| 212 if (need_input_method_set_) { | |
| 213 if (chromeos::ChangeInputMethod(input_method_status_connection_, | |
| 214 active_input_method_.c_str())) { | |
| 215 need_input_method_set_ = false; | |
| 216 completed = true; | |
| 217 active_input_methods_are_changed = true; | |
| 218 } | |
| 219 } else { | |
| 220 completed = true; | |
| 221 } | |
| 222 } | |
| 223 } | |
| 224 | |
| 225 if (completed) { | |
| 226 timer_.Stop(); // no-op if it's not running. | |
| 227 } else { | |
| 228 if (!timer_.IsRunning()) { | |
| 229 static const int64 kTimerIntervalInMsec = 100; | |
| 230 timer_.Start(base::TimeDelta::FromMilliseconds(kTimerIntervalInMsec), | |
| 231 this, &InputMethodLibraryImpl::FlushImeConfig); | |
| 232 } | |
| 233 } | |
| 234 if (active_input_methods_are_changed) { | |
| 235 FOR_EACH_OBSERVER(Observer, observers_, ActiveInputMethodsChanged(this)); | |
| 236 } | |
| 237 } | |
| 238 | |
| 239 // static | |
| 240 void InputMethodLibraryImpl::InputMethodChangedHandler( | |
| 241 void* object, const chromeos::InputMethodDescriptor& current_input_method) { | |
| 242 InputMethodLibraryImpl* input_method_library = | |
| 243 static_cast<InputMethodLibraryImpl*>(object); | |
| 244 input_method_library->UpdateCurrentInputMethod(current_input_method); | |
| 245 } | |
| 246 | |
| 247 // static | |
| 248 void InputMethodLibraryImpl::RegisterPropertiesHandler( | |
| 249 void* object, const ImePropertyList& prop_list) { | |
| 250 InputMethodLibraryImpl* input_method_library = | |
| 251 static_cast<InputMethodLibraryImpl*>(object); | |
| 252 input_method_library->RegisterProperties(prop_list); | |
| 253 } | |
| 254 | |
| 255 // static | |
| 256 void InputMethodLibraryImpl::UpdatePropertyHandler( | |
| 257 void* object, const ImePropertyList& prop_list) { | |
| 258 InputMethodLibraryImpl* input_method_library = | |
| 259 static_cast<InputMethodLibraryImpl*>(object); | |
| 260 input_method_library->UpdateProperty(prop_list); | |
| 261 } | |
| 262 | |
| 263 // static | |
| 264 void InputMethodLibraryImpl::ConnectionChangeHandler(void* object, | |
| 265 bool connected) { | |
| 266 InputMethodLibraryImpl* input_method_library = | |
| 267 static_cast<InputMethodLibraryImpl*>(object); | |
| 268 input_method_library->ime_connected_ = connected; | |
| 269 if (connected) { | |
| 270 input_method_library->pending_config_requests_.clear(); | |
| 271 input_method_library->pending_config_requests_.insert( | |
| 272 input_method_library->current_config_values_.begin(), | |
| 273 input_method_library->current_config_values_.end()); | |
| 274 // When the IME process starts up, the hardware layout will be the current | |
| 275 // method. If this is not correct, we'll need to explicitly change it. | |
| 276 if (input_method_library->active_input_method_ != kHardwareKeyboardLayout) { | |
| 277 input_method_library->need_input_method_set_ = true; | |
| 278 } | |
| 279 input_method_library->FlushImeConfig(); | |
| 280 } else { | |
| 281 // Stop attempting to resend config data, since it will continue to fail. | |
| 282 input_method_library->timer_.Stop(); // no-op if it's not running. | |
| 283 } | |
| 284 } | |
| 285 | |
| 286 bool InputMethodLibraryImpl::EnsureStarted() { | |
| 287 if (!input_method_status_connection_) { | |
| 288 input_method_status_connection_ = chromeos::MonitorInputMethodStatus( | |
| 289 this, | |
| 290 &InputMethodChangedHandler, | |
| 291 &RegisterPropertiesHandler, | |
| 292 &UpdatePropertyHandler, | |
| 293 &ConnectionChangeHandler); | |
| 294 } | |
| 295 return true; | |
| 296 } | |
| 297 | |
| 298 bool InputMethodLibraryImpl::EnsureLoadedAndStarted() { | |
| 299 return CrosLibrary::Get()->EnsureLoaded() && | |
| 300 EnsureStarted(); | |
| 301 } | |
| 302 | |
| 303 void InputMethodLibraryImpl::UpdateCurrentInputMethod( | |
| 304 const chromeos::InputMethodDescriptor& new_input_method) { | |
| 305 // Make sure we run on UI thread. | |
| 306 if (!ChromeThread::CurrentlyOn(ChromeThread::UI)) { | |
| 307 DLOG(INFO) << "UpdateCurrentInputMethod (Background thread)"; | |
| 308 ChromeThread::PostTask( | |
| 309 ChromeThread::UI, FROM_HERE, | |
| 310 // NewRunnableMethod() copies |new_input_method| by value. | |
| 311 NewRunnableMethod( | |
| 312 this, &InputMethodLibraryImpl::UpdateCurrentInputMethod, | |
| 313 new_input_method)); | |
| 314 return; | |
| 315 } | |
| 316 | |
| 317 DLOG(INFO) << "UpdateCurrentInputMethod (UI thread)"; | |
| 318 // Change the keyboard layout to a preferred layout for the input method. | |
| 319 CrosLibrary::Get()->GetKeyboardLibrary()->SetCurrentKeyboardLayoutByName( | |
| 320 new_input_method.keyboard_layout); | |
| 321 | |
| 322 if (current_input_method_.id != new_input_method.id) { | |
| 323 previous_input_method_ = current_input_method_; | |
| 324 current_input_method_ = new_input_method; | |
| 325 } | |
| 326 FOR_EACH_OBSERVER(Observer, observers_, InputMethodChanged(this)); | |
| 327 } | |
| 328 | |
| 329 void InputMethodLibraryImpl::RegisterProperties( | |
| 330 const ImePropertyList& prop_list) { | |
| 331 if (!ChromeThread::CurrentlyOn(ChromeThread::UI)) { | |
| 332 ChromeThread::PostTask( | |
| 333 ChromeThread::UI, FROM_HERE, | |
| 334 NewRunnableMethod( | |
| 335 this, &InputMethodLibraryImpl::RegisterProperties, prop_list)); | |
| 336 return; | |
| 337 } | |
| 338 | |
| 339 // |prop_list| might be empty. This means "clear all properties." | |
| 340 current_ime_properties_ = prop_list; | |
| 341 FOR_EACH_OBSERVER(Observer, observers_, ImePropertiesChanged(this)); | |
| 342 } | |
| 343 | |
| 344 void InputMethodLibraryImpl::UpdateProperty(const ImePropertyList& prop_list) { | |
| 345 if (!ChromeThread::CurrentlyOn(ChromeThread::UI)) { | |
| 346 ChromeThread::PostTask( | |
| 347 ChromeThread::UI, FROM_HERE, | |
| 348 NewRunnableMethod( | |
| 349 this, &InputMethodLibraryImpl::UpdateProperty, prop_list)); | |
| 350 return; | |
| 351 } | |
| 352 | |
| 353 for (size_t i = 0; i < prop_list.size(); ++i) { | |
| 354 FindAndUpdateProperty(prop_list[i], ¤t_ime_properties_); | |
| 355 } | |
| 356 FOR_EACH_OBSERVER(Observer, observers_, ImePropertiesChanged(this)); | |
| 357 } | |
| 358 | |
| 359 void InputMethodLibraryImpl::StartInputMethodProcesses() { | |
| 360 ime_running_ = true; | |
| 361 MaybeLaunchIme(); | |
| 362 } | |
| 363 | |
| 364 void InputMethodLibraryImpl::MaybeLaunchIme() { | |
| 365 if (!ime_running_) { | |
| 366 return; | |
| 367 } | |
| 368 | |
| 369 // TODO(zork): export "LD_PRELOAD=/usr/lib/libcrash.so" | |
| 370 GSpawnFlags flags = G_SPAWN_DO_NOT_REAP_CHILD; | |
| 371 if (ime_handle_ == 0) { | |
| 372 GError *error = NULL; | |
| 373 gchar **argv; | |
| 374 gint argc; | |
| 375 | |
| 376 if (!g_shell_parse_argv( | |
| 377 "/usr/bin/ibus-daemon --panel=disable --cache=none --restart", | |
| 378 &argc, &argv, &error)) { | |
| 379 LOG(ERROR) << "Could not parse command: " << error->message; | |
| 380 g_error_free(error); | |
| 381 return; | |
| 382 } | |
| 383 | |
| 384 error = NULL; | |
| 385 int handle; | |
| 386 // TODO(zork): Send output to /var/log/ibus.log | |
| 387 gboolean result = g_spawn_async(NULL, argv, NULL, | |
| 388 flags, NULL, NULL, | |
| 389 &handle, &error); | |
| 390 g_strfreev(argv); | |
| 391 if (!result) { | |
| 392 LOG(ERROR) << "Could not launch ime: " << error->message; | |
| 393 g_error_free(error); | |
| 394 return; | |
| 395 } | |
| 396 ime_handle_ = handle; | |
| 397 g_child_watch_add(ime_handle_, (GChildWatchFunc)OnImeShutdown, this); | |
| 398 } | |
| 399 | |
| 400 if (candidate_window_handle_ == 0) { | |
| 401 GError *error = NULL; | |
| 402 gchar **argv; | |
| 403 gint argc; | |
| 404 | |
| 405 if (!g_shell_parse_argv("/opt/google/chrome/candidate_window", | |
| 406 &argc, &argv, &error)) { | |
| 407 LOG(ERROR) << "Could not parse command: " << error->message; | |
| 408 g_error_free(error); | |
| 409 return; | |
| 410 } | |
| 411 | |
| 412 error = NULL; | |
| 413 int handle; | |
| 414 gboolean result = g_spawn_async(NULL, argv, NULL, | |
| 415 flags, NULL, NULL, | |
| 416 &handle, &error); | |
| 417 g_strfreev(argv); | |
| 418 if (!result) { | |
| 419 LOG(ERROR) << "Could not launch ime candidate window" << error->message; | |
| 420 g_error_free(error); | |
| 421 return; | |
| 422 } | |
| 423 candidate_window_handle_ = handle; | |
| 424 g_child_watch_add(candidate_window_handle_, | |
| 425 (GChildWatchFunc)OnImeShutdown, this); | |
| 426 } | |
| 427 } | |
| 428 | |
| 429 void InputMethodLibraryImpl::StopInputMethodProcesses() { | |
| 430 ime_running_ = false; | |
| 431 if (ime_handle_) { | |
| 432 kill(ime_handle_, SIGTERM); | |
| 433 ime_handle_ = 0; | |
| 434 } | |
| 435 if (candidate_window_handle_) { | |
| 436 kill(candidate_window_handle_, SIGTERM); | |
| 437 candidate_window_handle_ = 0; | |
| 438 } | |
| 439 } | |
| 440 | |
| 441 void InputMethodLibraryImpl::SetDeferImeStartup(bool defer) { | |
| 442 defer_ime_startup_ = defer; | |
| 443 } | |
| 444 | |
| 445 // static | |
| 446 void InputMethodLibraryImpl::OnImeShutdown(int pid, int status, | |
| 447 InputMethodLibraryImpl* library) { | |
| 448 g_spawn_close_pid(pid); | |
| 449 if (library->ime_handle_ == pid) { | |
| 450 library->ime_handle_ = 0; | |
| 451 } else if (library->candidate_window_handle_ == pid) { | |
| 452 library->candidate_window_handle_ = 0; | |
| 453 } | |
| 454 | |
| 455 library->MaybeLaunchIme(); | |
| 456 } | |
| 457 | |
| 458 } // namespace chromeos | 599 } // namespace chromeos |
| 600 |
| 601 // Allows InvokeLater without adding refcounting. This class is a Singleton and |
| 602 // won't be deleted until it's last InvokeLater is run. |
| 603 DISABLE_RUNNABLE_METHOD_REFCOUNT(chromeos::InputMethodLibraryImpl); |
| 604 |
| OLD | NEW |