1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/chromeos/input_method/input_method_engine_ibus.h"
8 #include <X11/keysymdef.h>
11 #include "base/logging.h"
12 #include "base/memory/scoped_ptr.h"
13 #include "base/strings/string_number_conversions.h"
14 #include "base/strings/string_util.h"
15 #include "base/strings/utf_string_conversions.h"
16 #include "chromeos/dbus/dbus_thread_manager.h"
17 #include "chromeos/dbus/ibus/ibus_client.h"
18 #include "chromeos/dbus/ibus/ibus_component.h"
19 #include "chromeos/dbus/ibus/ibus_engine_factory_service.h"
20 #include "chromeos/dbus/ibus/ibus_engine_service.h"
21 #include "chromeos/dbus/ibus/ibus_text.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/input_method_manager.h"
27 #include "dbus/object_path.h"
30 const char* kErrorNotActive = "IME is not active";
31 const char* kErrorWrongContext = "Context is not active";
32 const char* kCandidateNotFound = "Candidate not found";
33 const char* kEngineBusPrefix = "org.freedesktop.IBus.";
36 const uint32 kIBusAltKeyMask = 1 << 3;
37 const uint32 kIBusCtrlKeyMask = 1 << 2;
38 const uint32 kIBusShiftKeyMask = 1 << 0;
39 const uint32 kIBusCapsLockMask = 1 << 1;
40 const uint32 kIBusKeyReleaseMask = 1 << 30;
43 InputMethodEngineIBus::InputMethodEngineIBus()
48 aux_text_(new IBusText()),
49 aux_text_visible_(false),
51 preedit_text_(new IBusText()),
53 component_(new IBusComponent()),
54 candidate_window_(new input_method::CandidateWindow()),
55 window_visible_(false),
56 weak_ptr_factory_(this) {
59 InputMethodEngineIBus::~InputMethodEngineIBus() {
60 input_method::InputMethodManager::Get()->RemoveInputMethodExtension(ibus_id_);
62 // Do not unset engine before removing input method extension, above function
63 // may call reset function of engine object.
64 // TODO(nona): Call Reset manually here and remove relevant code from
65 // InputMethodManager once ibus-daemon is gone. (crbug.com/158273)
66 if (!object_path_.value().empty()) {
67 GetCurrentService()->UnsetEngine(this);
68 DBusThreadManager::Get()->RemoveIBusEngineService(object_path_);
72 void InputMethodEngineIBus::Initialize(
73 InputMethodEngine::Observer* observer,
74 const char* engine_name,
75 const char* extension_id,
76 const char* engine_id,
77 const char* description,
78 const std::vector<std::string>& languages,
79 const std::vector<std::string>& layouts,
80 const GURL& options_page,
82 DCHECK(observer) << "Observer must not be null.";
85 engine_id_ = engine_id;
87 input_method::InputMethodManager* manager =
88 input_method::InputMethodManager::Get();
89 ComponentExtensionIMEManager* comp_ext_ime_manager
90 = manager->GetComponentExtensionIMEManager();
92 if (comp_ext_ime_manager->IsInitialized() &&
93 comp_ext_ime_manager->IsWhitelistedExtension(extension_id)) {
94 ibus_id_ = comp_ext_ime_manager->GetId(extension_id, engine_id);
96 ibus_id_ = extension_ime_util::GetInputMethodID(extension_id, engine_id);
99 component_.reset(new IBusComponent());
100 component_->set_name(std::string(kEngineBusPrefix) + std::string(engine_id));
101 component_->set_description(description);
102 component_->set_author(engine_name);
104 // TODO(nona): Remove IBusComponent once ibus is gone.
105 IBusComponent::EngineDescription engine_desc;
106 engine_desc.engine_id = ibus_id_;
107 engine_desc.display_name = description;
108 engine_desc.description = description;
109 engine_desc.language_code = (languages.empty()) ? "" : languages[0];
110 engine_desc.author = ibus_id_;
112 component_->mutable_engine_description()->push_back(engine_desc);
113 manager->AddInputMethodExtension(ibus_id_, engine_name, layouts, languages,
115 // If connection is avaiable, register component. If there are no connection
116 // to ibus-daemon, OnConnected callback will register component instead.
121 void InputMethodEngineIBus::StartIme() {
122 input_method::InputMethodManager* manager =
123 input_method::InputMethodManager::Get();
124 if (manager && ibus_id_ == manager->GetCurrentInputMethod().id())
128 bool InputMethodEngineIBus::SetComposition(
134 const std::vector<SegmentInfo>& segments,
135 std::string* error) {
137 *error = kErrorNotActive;
140 if (context_id != context_id_ || context_id_ == -1) {
141 *error = kErrorWrongContext;
145 preedit_cursor_ = cursor;
146 preedit_text_.reset(new IBusText());
147 preedit_text_->set_text(text);
149 preedit_text_->mutable_selection_attributes()->clear();
150 IBusText::SelectionAttribute selection;
151 selection.start_index = selection_start;
152 selection.end_index = selection_end;
153 preedit_text_->mutable_selection_attributes()->push_back(selection);
155 // TODO: Add support for displaying selected text in the composition string.
156 for (std::vector<SegmentInfo>::const_iterator segment = segments.begin();
157 segment != segments.end(); ++segment) {
158 IBusText::UnderlineAttribute underline;
160 switch (segment->style) {
161 case SEGMENT_STYLE_UNDERLINE:
162 underline.type = IBusText::IBUS_TEXT_UNDERLINE_SINGLE;
164 case SEGMENT_STYLE_DOUBLE_UNDERLINE:
165 underline.type = IBusText::IBUS_TEXT_UNDERLINE_DOUBLE;
171 underline.start_index = segment->start;
172 underline.end_index = segment->end;
173 preedit_text_->mutable_underline_attributes()->push_back(underline);
176 // TODO(nona): Makes focus out mode configuable, if necessary.
177 GetCurrentService()->UpdatePreedit(
178 *preedit_text_.get(),
181 IBusEngineService::IBUS_ENGINE_PREEEDIT_FOCUS_OUT_MODE_COMMIT);
185 bool InputMethodEngineIBus::ClearComposition(int context_id,
186 std::string* error) {
188 *error = kErrorNotActive;
191 if (context_id != context_id_ || context_id_ == -1) {
192 *error = kErrorWrongContext;
197 preedit_text_.reset(new IBusText());
198 GetCurrentService()->UpdatePreedit(
199 *preedit_text_.get(),
202 IBusEngineService::IBUS_ENGINE_PREEEDIT_FOCUS_OUT_MODE_COMMIT);
206 bool InputMethodEngineIBus::CommitText(int context_id, const char* text,
207 std::string* error) {
209 // TODO: Commit the text anyways.
210 *error = kErrorNotActive;
213 if (context_id != context_id_ || context_id_ == -1) {
214 *error = kErrorWrongContext;
218 IBusBridge::Get()->GetInputContextHandler()->CommitText(text);
222 const InputMethodEngineIBus::CandidateWindowProperty&
223 InputMethodEngineIBus::GetCandidateWindowProperty() const {
224 return candidate_window_property_;
227 void InputMethodEngineIBus::SetCandidateWindowProperty(
228 const CandidateWindowProperty& property) {
229 // Type conversion from InputMethodEngine::CandidateWindowProperty to
230 // CandidateWindow::CandidateWindowProperty defined in chromeos/ime/.
231 input_method::CandidateWindow::CandidateWindowProperty dest_property;
232 dest_property.page_size = property.page_size;
233 dest_property.is_cursor_visible = property.is_cursor_visible;
234 dest_property.is_vertical = property.is_vertical;
235 dest_property.show_window_at_composition =
236 property.show_window_at_composition;
237 dest_property.cursor_position =
238 candidate_window_->GetProperty().cursor_position;
239 candidate_window_->SetProperty(dest_property);
240 candidate_window_property_ = property;
243 IBusPanelCandidateWindowHandlerInterface* cw_handler =
244 IBusBridge::Get()->GetCandidateWindowHandler();
246 cw_handler->UpdateLookupTable(*candidate_window_, window_visible_);
250 bool InputMethodEngineIBus::SetCandidateWindowVisible(bool visible,
251 std::string* error) {
253 *error = kErrorNotActive;
257 window_visible_ = visible;
258 IBusPanelCandidateWindowHandlerInterface* cw_handler =
259 IBusBridge::Get()->GetCandidateWindowHandler();
261 cw_handler->UpdateLookupTable(*candidate_window_, window_visible_);
265 void InputMethodEngineIBus::SetCandidateWindowAuxText(const char* text) {
266 aux_text_->set_text(text);
268 // Should not show auxiliary text if the whole window visibility is false.
269 GetCurrentService()->UpdateAuxiliaryText(
271 window_visible_ && aux_text_visible_);
275 void InputMethodEngineIBus::SetCandidateWindowAuxTextVisible(bool visible) {
276 aux_text_visible_ = visible;
278 // Should not show auxiliary text if the whole window visibility is false.
279 GetCurrentService()->UpdateAuxiliaryText(
281 window_visible_ && aux_text_visible_);
285 bool InputMethodEngineIBus::SetCandidates(
287 const std::vector<Candidate>& candidates,
288 std::string* error) {
290 *error = kErrorNotActive;
293 if (context_id != context_id_ || context_id_ == -1) {
294 *error = kErrorWrongContext;
298 // TODO: Nested candidates
299 candidate_ids_.clear();
300 candidate_indexes_.clear();
301 candidate_window_->mutable_candidates()->clear();
302 for (std::vector<Candidate>::const_iterator ix = candidates.begin();
303 ix != candidates.end(); ++ix) {
304 input_method::CandidateWindow::Entry entry;
305 entry.value = ix->value;
306 entry.label = ix->label;
307 entry.annotation = ix->annotation;
308 entry.description_title = ix->usage.title;
309 entry.description_body = ix->usage.body;
311 // Store a mapping from the user defined ID to the candidate index.
312 candidate_indexes_[ix->id] = candidate_ids_.size();
313 candidate_ids_.push_back(ix->id);
315 candidate_window_->mutable_candidates()->push_back(entry);
318 IBusPanelCandidateWindowHandlerInterface* cw_handler =
319 IBusBridge::Get()->GetCandidateWindowHandler();
321 cw_handler->UpdateLookupTable(*candidate_window_, window_visible_);
326 bool InputMethodEngineIBus::SetCursorPosition(int context_id, int candidate_id,
327 std::string* error) {
329 *error = kErrorNotActive;
332 if (context_id != context_id_ || context_id_ == -1) {
333 *error = kErrorWrongContext;
337 std::map<int, int>::const_iterator position =
338 candidate_indexes_.find(candidate_id);
339 if (position == candidate_indexes_.end()) {
340 *error = kCandidateNotFound;
344 candidate_window_->set_cursor_position(position->second);
345 IBusPanelCandidateWindowHandlerInterface* cw_handler =
346 IBusBridge::Get()->GetCandidateWindowHandler();
348 cw_handler->UpdateLookupTable(*candidate_window_, window_visible_);
352 bool InputMethodEngineIBus::SetMenuItems(const std::vector<MenuItem>& items) {
353 return UpdateMenuItems(items);
356 bool InputMethodEngineIBus::UpdateMenuItems(
357 const std::vector<MenuItem>& items) {
361 input_method::InputMethodPropertyList property_list;
362 for (std::vector<MenuItem>::const_iterator item = items.begin();
363 item != items.end(); ++item) {
364 input_method::InputMethodProperty property;
365 MenuItemToProperty(*item, &property);
366 property_list.push_back(property);
369 IBusPanelPropertyHandlerInterface* handler =
370 IBusBridge::Get()->GetPropertyHandler();
372 handler->RegisterProperties(property_list);
377 bool InputMethodEngineIBus::IsActive() const {
381 void InputMethodEngineIBus::KeyEventDone(input_method::KeyEventHandle* key_data,
383 KeyEventDoneCallback* callback =
384 reinterpret_cast<KeyEventDoneCallback*>(key_data);
385 callback->Run(handled);
389 bool InputMethodEngineIBus::DeleteSurroundingText(int context_id,
391 size_t number_of_chars,
392 std::string* error) {
394 *error = kErrorNotActive;
397 if (context_id != context_id_ || context_id_ == -1) {
398 *error = kErrorWrongContext;
402 if (offset < 0 && static_cast<size_t>(-1 * offset) != size_t(number_of_chars))
403 return false; // Currently we can only support preceding text.
405 // TODO(nona): Return false if there is ongoing composition.
406 GetCurrentService()->DeleteSurroundingText(offset, number_of_chars);
410 void InputMethodEngineIBus::FocusIn(ibus::TextInputType text_input_type) {
414 context_id_ = next_context_id_;
417 InputContext context;
418 context.id = context_id_;
420 context.type = "text";
422 observer_->OnFocus(context);
425 void InputMethodEngineIBus::FocusOut() {
429 int context_id = context_id_;
431 observer_->OnBlur(context_id);
434 void InputMethodEngineIBus::Enable() {
436 observer_->OnActivate(engine_id_);
437 FocusIn(ibus::TEXT_INPUT_TYPE_TEXT);
439 // Calls RequireSurroundingText once here to notify ibus-daemon to send
440 // surrounding text to this engine.
441 GetCurrentService()->RequireSurroundingText();
444 void InputMethodEngineIBus::Disable() {
446 observer_->OnDeactivated(engine_id_);
449 void InputMethodEngineIBus::PropertyActivate(const std::string& property_name) {
450 observer_->OnMenuItemActivated(engine_id_, property_name);
453 void InputMethodEngineIBus::PropertyShow(
454 const std::string& property_name) {
457 void InputMethodEngineIBus::PropertyHide(
458 const std::string& property_name) {
461 void InputMethodEngineIBus::SetCapability(
462 IBusCapability capability) {
465 void InputMethodEngineIBus::Reset() {
466 observer_->OnReset(engine_id_);
469 void InputMethodEngineIBus::ProcessKeyEvent(
473 const KeyEventDoneCallback& callback) {
475 KeyEventDoneCallback *handler = new KeyEventDoneCallback();
479 event.type = !(state & kIBusKeyReleaseMask) ? "keydown" : "keyup";
480 event.key = input_method::GetIBusKey(keysym);
481 event.code = input_method::GetIBusKeyCode(keycode);
482 event.alt_key = state & kIBusAltKeyMask;
483 event.ctrl_key = state & kIBusCtrlKeyMask;
484 event.shift_key = state & kIBusShiftKeyMask;
485 event.caps_lock = state & kIBusCapsLockMask;
486 observer_->OnKeyEvent(
489 reinterpret_cast<input_method::KeyEventHandle*>(handler));
492 void InputMethodEngineIBus::CandidateClicked(uint32 index,
493 ibus::IBusMouseButton button,
495 if (index > candidate_ids_.size()) {
499 MouseButtonEvent pressed_button;
501 case ibus::IBUS_MOUSE_BUTTON_LEFT:
502 pressed_button = MOUSE_BUTTON_LEFT;
504 case ibus::IBUS_MOUSE_BUTTON_MIDDLE:
505 pressed_button = MOUSE_BUTTON_MIDDLE;
507 case ibus::IBUS_MOUSE_BUTTON_RIGHT:
508 pressed_button = MOUSE_BUTTON_RIGHT;
511 DVLOG(1) << "Unknown button: " << button;
512 pressed_button = MOUSE_BUTTON_LEFT;
516 observer_->OnCandidateClicked(
517 engine_id_, candidate_ids_.at(index), pressed_button);
520 void InputMethodEngineIBus::SetSurroundingText(const std::string& text,
523 observer_->OnSurroundingTextChanged(engine_id_,
525 static_cast<int>(cursor_pos),
526 static_cast<int>(anchor_pos));
529 IBusEngineService* InputMethodEngineIBus::GetCurrentService() {
530 return DBusThreadManager::Get()->GetIBusEngineService(object_path_);
533 void InputMethodEngineIBus::MenuItemToProperty(
534 const MenuItem& item,
535 input_method::InputMethodProperty* property) {
536 property->key = item.id;
538 if (item.modified & MENU_ITEM_MODIFIED_LABEL) {
539 property->label = item.label;
541 if (item.modified & MENU_ITEM_MODIFIED_VISIBLE) {
542 // TODO(nona): Implement it.
544 if (item.modified & MENU_ITEM_MODIFIED_CHECKED) {
545 property->is_selection_item_checked = item.checked;
547 if (item.modified & MENU_ITEM_MODIFIED_ENABLED) {
548 // TODO(nona): implement sensitive entry(crbug.com/140192).
550 if (item.modified & MENU_ITEM_MODIFIED_STYLE) {
551 if (!item.children.empty()) {
552 // TODO(nona): Implement it.
554 switch (item.style) {
555 case MENU_ITEM_STYLE_NONE:
558 case MENU_ITEM_STYLE_CHECK:
559 // TODO(nona): Implement it.
561 case MENU_ITEM_STYLE_RADIO:
562 property->is_selection_item = true;
564 case MENU_ITEM_STYLE_SEPARATOR:
565 // TODO(nona): Implement it.
571 // TODO(nona): Support item.children.
574 void InputMethodEngineIBus::OnConnected() {
578 void InputMethodEngineIBus::OnDisconnected() {
581 bool InputMethodEngineIBus::IsConnected() {
582 return DBusThreadManager::Get()->GetIBusClient() != NULL;
585 void InputMethodEngineIBus::RegisterComponent() {
586 IBusClient* client = DBusThreadManager::Get()->GetIBusClient();
587 client->RegisterComponent(
589 base::Bind(&InputMethodEngineIBus::OnComponentRegistered,
590 weak_ptr_factory_.GetWeakPtr()),
591 base::Bind(&InputMethodEngineIBus::OnComponentRegistrationFailed,
592 weak_ptr_factory_.GetWeakPtr()));
595 void InputMethodEngineIBus::OnComponentRegistered() {
596 DBusThreadManager::Get()->GetIBusEngineFactoryService()->
597 SetCreateEngineHandler(ibus_id_,
599 &InputMethodEngineIBus::CreateEngineHandler,
600 weak_ptr_factory_.GetWeakPtr()));
603 void InputMethodEngineIBus::OnComponentRegistrationFailed() {
604 DVLOG(1) << "Failed to register input method components.";
605 // TODO(nona): Implement error handling.
608 void InputMethodEngineIBus::CreateEngineHandler(
609 const IBusEngineFactoryService::CreateEngineResponseSender& sender) {
610 GetCurrentService()->UnsetEngine(this);
611 DBusThreadManager::Get()->RemoveIBusEngineService(object_path_);
613 object_path_ = DBusThreadManager::Get()->GetIBusEngineFactoryService()->
614 GenerateUniqueObjectPath();
616 GetCurrentService()->SetEngine(this);
617 sender.Run(object_path_);
620 } // namespace chromeos