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 |