Home | History | Annotate | Download | only in input_method
      1 // Copyright 2013 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 "chrome/browser/chromeos/input_method/input_method_engine.h"
      6 
      7 #define XK_MISCELLANY
      8 #include <X11/keysymdef.h>
      9 #include <X11/X.h>
     10 #include <X11/Xlib.h>
     11 #include <X11/Xutil.h>
     12 #undef FocusIn
     13 #undef FocusOut
     14 #include <map>
     15 
     16 #include "ash/shell.h"
     17 #include "base/logging.h"
     18 #include "base/memory/scoped_ptr.h"
     19 #include "base/strings/string_number_conversions.h"
     20 #include "base/strings/string_util.h"
     21 #include "base/strings/utf_string_conversions.h"
     22 #include "chromeos/ime/candidate_window.h"
     23 #include "chromeos/ime/component_extension_ime_manager.h"
     24 #include "chromeos/ime/extension_ime_util.h"
     25 #include "chromeos/ime/ibus_keymap.h"
     26 #include "chromeos/ime/ibus_text.h"
     27 #include "chromeos/ime/input_method_manager.h"
     28 #include "ui/events/event.h"
     29 #include "ui/events/keycodes/keyboard_code_conversion_x.h"
     30 #include "ui/keyboard/keyboard_controller.h"
     31 
     32 namespace chromeos {
     33 const char* kErrorNotActive = "IME is not active";
     34 const char* kErrorWrongContext = "Context is not active";
     35 const char* kCandidateNotFound = "Candidate not found";
     36 const char* kEngineBusPrefix = "org.freedesktop.IBus.";
     37 
     38 namespace {
     39 
     40 // Notifies InputContextHandler that the preedit is changed.
     41 void UpdatePreedit(const IBusText& ibus_text,
     42                    uint32 cursor_pos,
     43                    bool is_visible) {
     44   IBusInputContextHandlerInterface* input_context =
     45       IBusBridge::Get()->GetInputContextHandler();
     46   if (input_context)
     47     input_context->UpdatePreeditText(ibus_text, cursor_pos, is_visible);
     48 }
     49 
     50 // Notifies CandidateWindowHandler that the auxilary text is changed.
     51 // Auxilary text is usually footer text.
     52 void UpdateAuxiliaryText(const std::string& text, bool is_visible) {
     53   IBusPanelCandidateWindowHandlerInterface* candidate_window =
     54       IBusBridge::Get()->GetCandidateWindowHandler();
     55   if (candidate_window)
     56     candidate_window->UpdateAuxiliaryText(text, is_visible);
     57 }
     58 
     59 }  // namespace
     60 
     61 InputMethodEngine::InputMethodEngine()
     62     : focused_(false),
     63       active_(false),
     64       context_id_(0),
     65       next_context_id_(1),
     66       aux_text_visible_(false),
     67       observer_(NULL),
     68       preedit_text_(new IBusText()),
     69       preedit_cursor_(0),
     70       candidate_window_(new input_method::CandidateWindow()),
     71       window_visible_(false) {}
     72 
     73 InputMethodEngine::~InputMethodEngine() {
     74   input_method::InputMethodManager::Get()->RemoveInputMethodExtension(ibus_id_);
     75 }
     76 
     77 void InputMethodEngine::Initialize(
     78     InputMethodEngineInterface::Observer* observer,
     79     const char* engine_name,
     80     const char* extension_id,
     81     const char* engine_id,
     82     const std::vector<std::string>& languages,
     83     const std::vector<std::string>& layouts,
     84     const GURL& options_page,
     85     const GURL& input_view) {
     86   DCHECK(observer) << "Observer must not be null.";
     87 
     88   observer_ = observer;
     89   engine_id_ = engine_id;
     90 
     91   input_method::InputMethodManager* manager =
     92       input_method::InputMethodManager::Get();
     93   ComponentExtensionIMEManager* comp_ext_ime_manager
     94       = manager->GetComponentExtensionIMEManager();
     95 
     96   if (comp_ext_ime_manager->IsInitialized() &&
     97       comp_ext_ime_manager->IsWhitelistedExtension(extension_id)) {
     98     ibus_id_ = comp_ext_ime_manager->GetId(extension_id, engine_id);
     99   } else {
    100     ibus_id_ = extension_ime_util::GetInputMethodID(extension_id, engine_id);
    101   }
    102 
    103   input_view_url_ = input_view;
    104 
    105   manager->AddInputMethodExtension(ibus_id_, engine_name, layouts, languages,
    106                                    options_page, input_view, this);
    107   IBusBridge::Get()->SetEngineHandler(ibus_id_, this);
    108 }
    109 
    110 void InputMethodEngine::StartIme() {
    111   input_method::InputMethodManager* manager =
    112       input_method::InputMethodManager::Get();
    113   if (manager && ibus_id_ == manager->GetCurrentInputMethod().id())
    114     Enable();
    115 }
    116 
    117 bool InputMethodEngine::SetComposition(
    118     int context_id,
    119     const char* text,
    120     int selection_start,
    121     int selection_end,
    122     int cursor,
    123     const std::vector<SegmentInfo>& segments,
    124     std::string* error) {
    125   if (!active_) {
    126     *error = kErrorNotActive;
    127     return false;
    128   }
    129   if (context_id != context_id_ || context_id_ == -1) {
    130     *error = kErrorWrongContext;
    131     return false;
    132   }
    133 
    134   preedit_cursor_ = cursor;
    135   preedit_text_.reset(new IBusText());
    136   preedit_text_->set_text(text);
    137 
    138   preedit_text_->set_selection_start(selection_start);
    139   preedit_text_->set_selection_end(selection_end);
    140 
    141   // TODO: Add support for displaying selected text in the composition string.
    142   for (std::vector<SegmentInfo>::const_iterator segment = segments.begin();
    143        segment != segments.end(); ++segment) {
    144     IBusText::UnderlineAttribute underline;
    145 
    146     switch (segment->style) {
    147       case SEGMENT_STYLE_UNDERLINE:
    148         underline.type = IBusText::IBUS_TEXT_UNDERLINE_SINGLE;
    149         break;
    150       case SEGMENT_STYLE_DOUBLE_UNDERLINE:
    151         underline.type = IBusText::IBUS_TEXT_UNDERLINE_DOUBLE;
    152         break;
    153       default:
    154         continue;
    155     }
    156 
    157     underline.start_index = segment->start;
    158     underline.end_index = segment->end;
    159     preedit_text_->mutable_underline_attributes()->push_back(underline);
    160   }
    161 
    162   // TODO(nona): Makes focus out mode configuable, if necessary.
    163   UpdatePreedit(*preedit_text_, preedit_cursor_, true);
    164   return true;
    165 }
    166 
    167 bool InputMethodEngine::ClearComposition(int context_id,
    168                                          std::string* error)  {
    169   if (!active_) {
    170     *error = kErrorNotActive;
    171     return false;
    172   }
    173   if (context_id != context_id_ || context_id_ == -1) {
    174     *error = kErrorWrongContext;
    175     return false;
    176   }
    177 
    178   preedit_cursor_ = 0;
    179   preedit_text_.reset(new IBusText());
    180   UpdatePreedit(*preedit_text_, preedit_cursor_, false);
    181   return true;
    182 }
    183 
    184 bool InputMethodEngine::CommitText(int context_id, const char* text,
    185                                    std::string* error) {
    186   if (!active_) {
    187     // TODO: Commit the text anyways.
    188     *error = kErrorNotActive;
    189     return false;
    190   }
    191   if (context_id != context_id_ || context_id_ == -1) {
    192     *error = kErrorWrongContext;
    193     return false;
    194   }
    195 
    196   IBusBridge::Get()->GetInputContextHandler()->CommitText(text);
    197   return true;
    198 }
    199 
    200 const InputMethodEngine::CandidateWindowProperty&
    201 InputMethodEngine::GetCandidateWindowProperty() const {
    202   return candidate_window_property_;
    203 }
    204 
    205 void InputMethodEngine::SetCandidateWindowProperty(
    206     const CandidateWindowProperty& property) {
    207   // Type conversion from InputMethodEngineInterface::CandidateWindowProperty to
    208   // CandidateWindow::CandidateWindowProperty defined in chromeos/ime/.
    209   input_method::CandidateWindow::CandidateWindowProperty dest_property;
    210   dest_property.page_size = property.page_size;
    211   dest_property.is_cursor_visible = property.is_cursor_visible;
    212   dest_property.is_vertical = property.is_vertical;
    213   dest_property.show_window_at_composition =
    214       property.show_window_at_composition;
    215   dest_property.cursor_position =
    216       candidate_window_->GetProperty().cursor_position;
    217   candidate_window_->SetProperty(dest_property);
    218   candidate_window_property_ = property;
    219 
    220   if (active_) {
    221     IBusPanelCandidateWindowHandlerInterface* cw_handler =
    222         IBusBridge::Get()->GetCandidateWindowHandler();
    223     if (cw_handler)
    224       cw_handler->UpdateLookupTable(*candidate_window_, window_visible_);
    225   }
    226 }
    227 
    228 bool InputMethodEngine::SetCandidateWindowVisible(bool visible,
    229                                                   std::string* error) {
    230   if (!active_) {
    231     *error = kErrorNotActive;
    232     return false;
    233   }
    234 
    235   window_visible_ = visible;
    236   IBusPanelCandidateWindowHandlerInterface* cw_handler =
    237     IBusBridge::Get()->GetCandidateWindowHandler();
    238   if (cw_handler)
    239     cw_handler->UpdateLookupTable(*candidate_window_, window_visible_);
    240   return true;
    241 }
    242 
    243 void InputMethodEngine::SetCandidateWindowAuxText(const char* text) {
    244   aux_text_.assign(text);
    245   if (active_) {
    246     // Should not show auxiliary text if the whole window visibility is false.
    247     UpdateAuxiliaryText(aux_text_, window_visible_ && aux_text_visible_);
    248   }
    249 }
    250 
    251 void InputMethodEngine::SetCandidateWindowAuxTextVisible(bool visible) {
    252   aux_text_visible_ = visible;
    253   if (active_) {
    254     // Should not show auxiliary text if the whole window visibility is false.
    255     UpdateAuxiliaryText(aux_text_, window_visible_ && aux_text_visible_);
    256   }
    257 }
    258 
    259 bool InputMethodEngine::SetCandidates(
    260     int context_id,
    261     const std::vector<Candidate>& candidates,
    262     std::string* error) {
    263   if (!active_) {
    264     *error = kErrorNotActive;
    265     return false;
    266   }
    267   if (context_id != context_id_ || context_id_ == -1) {
    268     *error = kErrorWrongContext;
    269     return false;
    270   }
    271 
    272   // TODO: Nested candidates
    273   candidate_ids_.clear();
    274   candidate_indexes_.clear();
    275   candidate_window_->mutable_candidates()->clear();
    276   for (std::vector<Candidate>::const_iterator ix = candidates.begin();
    277        ix != candidates.end(); ++ix) {
    278     input_method::CandidateWindow::Entry entry;
    279     entry.value = ix->value;
    280     entry.label = ix->label;
    281     entry.annotation = ix->annotation;
    282     entry.description_title = ix->usage.title;
    283     entry.description_body = ix->usage.body;
    284 
    285     // Store a mapping from the user defined ID to the candidate index.
    286     candidate_indexes_[ix->id] = candidate_ids_.size();
    287     candidate_ids_.push_back(ix->id);
    288 
    289     candidate_window_->mutable_candidates()->push_back(entry);
    290   }
    291   if (active_) {
    292     IBusPanelCandidateWindowHandlerInterface* cw_handler =
    293       IBusBridge::Get()->GetCandidateWindowHandler();
    294     if (cw_handler)
    295       cw_handler->UpdateLookupTable(*candidate_window_, window_visible_);
    296   }
    297   return true;
    298 }
    299 
    300 bool InputMethodEngine::SetCursorPosition(int context_id, int candidate_id,
    301                                           std::string* error) {
    302   if (!active_) {
    303     *error = kErrorNotActive;
    304     return false;
    305   }
    306   if (context_id != context_id_ || context_id_ == -1) {
    307     *error = kErrorWrongContext;
    308     return false;
    309   }
    310 
    311   std::map<int, int>::const_iterator position =
    312       candidate_indexes_.find(candidate_id);
    313   if (position == candidate_indexes_.end()) {
    314     *error = kCandidateNotFound;
    315     return false;
    316   }
    317 
    318   candidate_window_->set_cursor_position(position->second);
    319   IBusPanelCandidateWindowHandlerInterface* cw_handler =
    320     IBusBridge::Get()->GetCandidateWindowHandler();
    321   if (cw_handler)
    322     cw_handler->UpdateLookupTable(*candidate_window_, window_visible_);
    323   return true;
    324 }
    325 
    326 bool InputMethodEngine::SetMenuItems(const std::vector<MenuItem>& items) {
    327   return UpdateMenuItems(items);
    328 }
    329 
    330 bool InputMethodEngine::UpdateMenuItems(
    331     const std::vector<MenuItem>& items) {
    332   if (!active_)
    333     return false;
    334 
    335   input_method::InputMethodPropertyList property_list;
    336   for (std::vector<MenuItem>::const_iterator item = items.begin();
    337        item != items.end(); ++item) {
    338     input_method::InputMethodProperty property;
    339     MenuItemToProperty(*item, &property);
    340     property_list.push_back(property);
    341   }
    342 
    343   input_method::InputMethodManager* manager =
    344       input_method::InputMethodManager::Get();
    345   if (manager)
    346     manager->SetCurrentInputMethodProperties(property_list);
    347 
    348   return true;
    349 }
    350 
    351 bool InputMethodEngine::IsActive() const {
    352   return active_;
    353 }
    354 
    355 void InputMethodEngine::KeyEventDone(input_method::KeyEventHandle* key_data,
    356                                      bool handled) {
    357   KeyEventDoneCallback* callback =
    358       reinterpret_cast<KeyEventDoneCallback*>(key_data);
    359   callback->Run(handled);
    360   delete callback;
    361 }
    362 
    363 bool InputMethodEngine::DeleteSurroundingText(int context_id,
    364                                               int offset,
    365                                               size_t number_of_chars,
    366                                               std::string* error) {
    367   if (!active_) {
    368     *error = kErrorNotActive;
    369     return false;
    370   }
    371   if (context_id != context_id_ || context_id_ == -1) {
    372     *error = kErrorWrongContext;
    373     return false;
    374   }
    375 
    376   if (offset < 0 && static_cast<size_t>(-1 * offset) != size_t(number_of_chars))
    377     return false;  // Currently we can only support preceding text.
    378 
    379   // TODO(nona): Return false if there is ongoing composition.
    380 
    381   IBusInputContextHandlerInterface* input_context =
    382       IBusBridge::Get()->GetInputContextHandler();
    383   if (input_context)
    384     input_context->DeleteSurroundingText(offset, number_of_chars);
    385 
    386   return true;
    387 }
    388 
    389 void InputMethodEngine::FocusIn(
    390     const IBusEngineHandlerInterface::InputContext& input_context) {
    391   focused_ = true;
    392   if (!active_)
    393     return;
    394   context_id_ = next_context_id_;
    395   ++next_context_id_;
    396 
    397   InputMethodEngineInterface::InputContext context;
    398   context.id = context_id_;
    399   switch (input_context.type) {
    400     case ui::TEXT_INPUT_TYPE_SEARCH:
    401       context.type = "search";
    402       break;
    403     case ui::TEXT_INPUT_TYPE_TELEPHONE:
    404       context.type = "tel";
    405       break;
    406     case ui::TEXT_INPUT_TYPE_URL:
    407       context.type = "url";
    408       break;
    409     case ui::TEXT_INPUT_TYPE_EMAIL:
    410       context.type = "email";
    411       break;
    412     case ui::TEXT_INPUT_TYPE_NUMBER:
    413       context.type = "number";
    414       break;
    415     default:
    416       context.type = "text";
    417       break;
    418   }
    419 
    420   observer_->OnFocus(context);
    421 }
    422 
    423 void InputMethodEngine::FocusOut() {
    424   focused_ = false;
    425   if (!active_)
    426     return;
    427   int context_id = context_id_;
    428   context_id_ = -1;
    429   observer_->OnBlur(context_id);
    430 }
    431 
    432 void InputMethodEngine::Enable() {
    433   active_ = true;
    434   observer_->OnActivate(engine_id_);
    435   IBusEngineHandlerInterface::InputContext context(ui::TEXT_INPUT_TYPE_TEXT,
    436                                                    ui::TEXT_INPUT_MODE_DEFAULT);
    437   FocusIn(context);
    438 
    439   keyboard::KeyboardController* keyboard_controller =
    440       ash::Shell::GetInstance()->keyboard_controller();
    441   if (keyboard_controller) {
    442     keyboard_controller->SetOverrideContentUrl(input_view_url_);
    443   }
    444 }
    445 
    446 void InputMethodEngine::Disable() {
    447   active_ = false;
    448   observer_->OnDeactivated(engine_id_);
    449 
    450   keyboard::KeyboardController* keyboard_controller =
    451       ash::Shell::GetInstance()->keyboard_controller();
    452   if (keyboard_controller) {
    453     GURL empty_url;
    454     keyboard_controller->SetOverrideContentUrl(empty_url);
    455   }
    456 }
    457 
    458 void InputMethodEngine::PropertyActivate(const std::string& property_name) {
    459   observer_->OnMenuItemActivated(engine_id_, property_name);
    460 }
    461 
    462 void InputMethodEngine::Reset() {
    463   observer_->OnReset(engine_id_);
    464 }
    465 
    466 namespace {
    467 void GetExtensionKeyboardEventFromKeyEvent(
    468     const ui::KeyEvent& event,
    469     InputMethodEngine::KeyboardEvent* ext_event) {
    470   DCHECK(event.type() == ui::ET_KEY_RELEASED ||
    471          event.type() == ui::ET_KEY_PRESSED);
    472   DCHECK(ext_event);
    473   ext_event->type = (event.type() == ui::ET_KEY_RELEASED) ? "keyup" : "keydown";
    474 
    475   ext_event->code = event.code();
    476   ext_event->alt_key = event.IsAltDown();
    477   ext_event->ctrl_key = event.IsControlDown();
    478   ext_event->shift_key = event.IsShiftDown();
    479   ext_event->caps_lock = event.IsCapsLockDown();
    480 
    481   uint32 ibus_keyval = 0;
    482   if (event.HasNativeEvent()) {
    483     const base::NativeEvent& native_event = event.native_event();
    484     DCHECK(native_event);
    485 
    486     XKeyEvent* x_key = &(static_cast<XEvent*>(native_event)->xkey);
    487     KeySym keysym = NoSymbol;
    488     ::XLookupString(x_key, NULL, 0, &keysym, NULL);
    489     ibus_keyval = keysym;
    490   } else {
    491     // Convert ui::KeyEvent.key_code to DOM UIEvent key.
    492     // XKeysymForWindowsKeyCode converts key_code to XKeySym, but it
    493     // assumes US layout and does not care about CapLock state.
    494     //
    495     // TODO(komatsu): Support CapsLock states.
    496     // TODO(komatsu): Support non-us keyboard layouts.
    497     ibus_keyval = ui::XKeysymForWindowsKeyCode(event.key_code(),
    498                                                event.IsShiftDown());
    499   }
    500   ext_event->key = input_method::GetIBusKey(ibus_keyval);
    501 }
    502 }  // namespace
    503 
    504 void InputMethodEngine::ProcessKeyEvent(
    505     const ui::KeyEvent& key_event,
    506     const KeyEventDoneCallback& callback) {
    507 
    508   KeyEventDoneCallback *handler = new KeyEventDoneCallback();
    509   *handler = callback;
    510 
    511   KeyboardEvent ext_event;
    512   GetExtensionKeyboardEventFromKeyEvent(key_event, &ext_event);
    513   observer_->OnKeyEvent(
    514       engine_id_,
    515       ext_event,
    516       reinterpret_cast<input_method::KeyEventHandle*>(handler));
    517 }
    518 
    519 void InputMethodEngine::CandidateClicked(uint32 index) {
    520   if (index > candidate_ids_.size()) {
    521     return;
    522   }
    523 
    524   // Only left button click is supported at this moment.
    525   observer_->OnCandidateClicked(
    526       engine_id_, candidate_ids_.at(index), MOUSE_BUTTON_LEFT);
    527 }
    528 
    529 void InputMethodEngine::SetSurroundingText(const std::string& text,
    530                                            uint32 cursor_pos,
    531                                            uint32 anchor_pos) {
    532   observer_->OnSurroundingTextChanged(engine_id_,
    533                                       text,
    534                                       static_cast<int>(cursor_pos),
    535                                       static_cast<int>(anchor_pos));
    536 }
    537 
    538 void InputMethodEngine::MenuItemToProperty(
    539     const MenuItem& item,
    540     input_method::InputMethodProperty* property) {
    541   property->key = item.id;
    542 
    543   if (item.modified & MENU_ITEM_MODIFIED_LABEL) {
    544     property->label = item.label;
    545   }
    546   if (item.modified & MENU_ITEM_MODIFIED_VISIBLE) {
    547     // TODO(nona): Implement it.
    548   }
    549   if (item.modified & MENU_ITEM_MODIFIED_CHECKED) {
    550     property->is_selection_item_checked = item.checked;
    551   }
    552   if (item.modified & MENU_ITEM_MODIFIED_ENABLED) {
    553     // TODO(nona): implement sensitive entry(crbug.com/140192).
    554   }
    555   if (item.modified & MENU_ITEM_MODIFIED_STYLE) {
    556     if (!item.children.empty()) {
    557       // TODO(nona): Implement it.
    558     } else {
    559       switch (item.style) {
    560         case MENU_ITEM_STYLE_NONE:
    561           NOTREACHED();
    562           break;
    563         case MENU_ITEM_STYLE_CHECK:
    564           // TODO(nona): Implement it.
    565           break;
    566         case MENU_ITEM_STYLE_RADIO:
    567           property->is_selection_item = true;
    568           break;
    569         case MENU_ITEM_STYLE_SEPARATOR:
    570           // TODO(nona): Implement it.
    571           break;
    572       }
    573     }
    574   }
    575 
    576   // TODO(nona): Support item.children.
    577 }
    578 
    579 }  // namespace chromeos
    580