Back to index

nux  3.0.0
InputMethodIBus.cpp
Go to the documentation of this file.
00001 /*
00002 * Copyright 2010 Inalogic® Inc.
00003 *
00004 * This program is free software: you can redistribute it and/or modify it
00005 * under the terms of the GNU Lesser General Public License, as
00006 * published by the  Free Software Foundation; either version 2.1 or 3.0
00007 * of the License.
00008 *
00009 * This program is distributed in the hope that it will be useful, but
00010 * WITHOUT ANY WARRANTY; without even the implied warranties of
00011 * MERCHANTABILITY, SATISFACTORY QUALITY or FITNESS FOR A PARTICULAR
00012 * PURPOSE.  See the applicable version of the GNU Lesser General Public
00013 * License for more details.
00014 *
00015 * You should have received a copy of both the GNU Lesser General Public
00016 * License along with this program. If not, see <http://www.gnu.org/licenses/>
00017 *
00018 * Authored by: Brandon Schaefer <brandontschaefer@gmail.com>
00019 *              Jay Taoko <jaytaoko@inalogic.com>
00020 *
00021 */
00022 
00023 
00024 #include "Nux.h"
00025 
00026 #include "InputMethodIBus.h"
00027 
00028 namespace nux
00029 {
00030 
00031   std::vector<Event> IBusIMEContext::hotkeys_;
00032 
00033   IBusBus* IBusIMEContext::bus_ = NULL;
00034 
00035   IBusIMEContext::IBusIMEContext(TextEntry* text_entry)
00036     : text_entry_(text_entry),
00037       context_(NULL),
00038       is_focused_(false)
00039   {
00040     // init ibus
00041     if (!bus_)
00042     {
00043       ibus_init();
00044       bus_ = ibus_bus_new();
00045     }
00046 
00047     // connect bus signals
00048     g_signal_connect(bus_, "connected",    G_CALLBACK(OnConnected_),     this);
00049     g_signal_connect(bus_, "disconnected", G_CALLBACK(OnDisconnected_),  this);
00050 
00051     if (ibus_bus_is_connected(bus_))
00052     {
00053       CreateContext();
00054     }
00055     else
00056     {
00057       nuxDebugMsg("[IBusIMEContext::IBusIMEContext] Can not connect to ibus");
00058     }
00059   }
00060 
00061   IBusIMEContext::~IBusIMEContext()
00062   {
00063     // disconnect bus signals
00064     g_signal_handlers_disconnect_by_func(bus_, reinterpret_cast<gpointer>(OnConnected_),    this);
00065     g_signal_handlers_disconnect_by_func(bus_, reinterpret_cast<gpointer>(OnDisconnected_), this);
00066 
00067     DestroyContext();
00068   }
00069 
00070   void IBusIMEContext::Focus()
00071   {
00072     if (is_focused_)
00073       return;
00074     is_focused_ = true;
00075 
00076     if (context_)
00077       ibus_input_context_focus_in(context_);
00078   }
00079 
00080   void IBusIMEContext::Blur()
00081   {
00082     if (!is_focused_)
00083       return;
00084 
00085     is_focused_ = false;
00086 
00087     if (context_)
00088       ibus_input_context_focus_out(context_);
00089   }
00090 
00091   void IBusIMEContext::Reset()
00092   {
00093     if (context_)
00094       ibus_input_context_reset(context_);
00095   }
00096 
00097   bool IBusIMEContext::FilterKeyEvent(const KeyEvent& event)
00098   {
00099     guint keyval = event.key_sym(); // todo(jaytaoko): ui::GdkKeyCodeForWindowsKeyCode(event.key_code(), event.IsShiftDown() ^ event.IsCapsLockDown());
00100 
00101     if (context_) {
00102       guint modifiers = 0;
00103 
00104       if (event.flags() & IBUS_IGNORED_MASK)
00105         return false;
00106 
00107       if (event.type() == EVENT_KEY_UP)
00108         modifiers |= IBUS_RELEASE_MASK;
00109 
00110       if (event.IsShiftDown())
00111         modifiers |= IBUS_SHIFT_MASK;
00112       if (event.IsControlDown())
00113         modifiers |= IBUS_CONTROL_MASK;
00114       if (event.IsAltDown())
00115         modifiers |= IBUS_MOD1_MASK;
00116       if (event.IsCapsLockDown())
00117         modifiers |= IBUS_LOCK_MASK;
00118 
00119       ibus_input_context_process_key_event_async(context_,
00120         keyval, event.key_code() - 8, modifiers,
00121         -1,
00122         NULL,
00123         reinterpret_cast<GAsyncReadyCallback>(ProcessKeyEventDone),
00124         new ProcessKeyEventData(this, event));
00125 
00126       return true;
00127     }
00128     return false;
00129   }
00130 
00131   void IBusIMEContext::SetSurrounding(const std::wstring& text, int cursor_pos)
00132   {
00133       // TODO(penghuang) support surrounding
00134   }
00135 
00136   void IBusIMEContext::CreateContext() {
00137     nuxAssert(bus_ != NULL);
00138     nuxAssert(ibus_bus_is_connected(bus_));
00139 
00140     if (!(context_ = ibus_bus_create_input_context(bus_, "nux")))
00141     {
00142       nuxDebugMsg("[IBusIMEContext::IBusIMEContext] Cannot create InputContext");
00143       return;
00144     }
00145 
00146     text_entry_->ime_active_ = false;
00147 
00148     // connect input context signals
00149     g_signal_connect(context_, "commit-text",         G_CALLBACK(OnCommitText_),        this);
00150     g_signal_connect(context_, "update-preedit-text", G_CALLBACK(OnUpdatePreeditText_), this);
00151     g_signal_connect(context_, "show-preedit-text",   G_CALLBACK(OnShowPreeditText_),   this);
00152     g_signal_connect(context_, "hide-preedit-text",   G_CALLBACK(OnHidePreeditText_),   this);
00153     g_signal_connect(context_, "enabled",             G_CALLBACK(OnEnable_),            this);
00154     g_signal_connect(context_, "disabled",            G_CALLBACK(OnDisable_),           this);
00155     g_signal_connect(context_, "destroy",             G_CALLBACK(OnDestroy_),           this);
00156 
00157     guint32 caps = IBUS_CAP_PREEDIT_TEXT |
00158       IBUS_CAP_FOCUS |
00159       IBUS_CAP_SURROUNDING_TEXT;
00160     ibus_input_context_set_capabilities(context_, caps);
00161 
00162     if (is_focused_)
00163       ibus_input_context_focus_in(context_);
00164 
00165     UpdateCursorLocation();
00166 
00167     IBusConfig* bus_conf = ibus_bus_get_config(bus_);
00168     g_signal_handlers_disconnect_by_func(bus_conf, reinterpret_cast<gpointer>(OnConfigChanged_), this);
00169     g_signal_connect(bus_conf, "value-changed", G_CALLBACK(OnConfigChanged_), this);
00170     UpdateHotkeys();
00171   }
00172 
00173   void IBusIMEContext::DestroyContext()
00174   {
00175     //nuxDebugMsg("***IBusIMEContext::DestroyContext***");
00176 
00177     if (ibus_bus_is_connected(bus_))
00178     {
00179       IBusConfig* bus_conf = ibus_bus_get_config(bus_);
00180       g_signal_handlers_disconnect_by_func(bus_conf, reinterpret_cast<gpointer>(OnConfigChanged_), this);
00181     }
00182 
00183     if (!context_)
00184       return;
00185 
00186     text_entry_->ResetPreedit();
00187     ibus_proxy_destroy(reinterpret_cast<IBusProxy *>(context_));
00188 
00189     nuxAssert(!context_);
00190   }
00191 
00192   bool IBusIMEContext::IsConnected() const
00193   {
00194     return context_;
00195   }
00196 
00197   void IBusIMEContext::UpdateCursorLocation()
00198   {
00199     nux::Rect strong, weak;
00200     text_entry_->GetCursorRects(&strong, &weak);
00201 
00202     // Get the position of the TextEntry in the Window.
00203     nux::Geometry geo = text_entry_->GetAbsoluteGeometry();
00204 
00205     // Get the Geometry of the window on the display.
00206     nux::Geometry window_geo = nux::GetGraphicsDisplay()->GetWindowGeometry();
00207 
00208     ibus_input_context_set_cursor_location(context_,
00209       geo.x + window_geo.x + strong.x,
00210       geo.y + window_geo.y,
00211       0,
00212       geo.height);
00213   }
00214 
00215   void IBusIMEContext::OnConnected(IBusBus *bus)
00216   {
00217     //nuxDebugMsg("***IBusIMEContext::OnConnected***");
00218     nuxAssert(bus_ == bus);
00219     if (ibus_bus_is_connected(bus_))
00220     {
00221       DestroyContext();
00222       CreateContext();
00223     }
00224   }
00225 
00226   void IBusIMEContext::OnDisconnected(IBusBus *bus)
00227   {
00228     //nuxDebugMsg("***IBusIMEContext::OnDisonnected***");
00229     hotkeys_.clear();
00230 
00231     DestroyContext();
00232   }
00233 
00234   void IBusIMEContext::OnConfigChanged(IBusConfig* config, gchar* section, gchar* name, GVariant* value)
00235   {
00236     if (g_strcmp0(section, "general/hotkey") == 0)
00237     {
00238       if (g_strcmp0(name, "trigger") == 0)
00239         UpdateHotkeys();
00240     }
00241   }
00242 
00243   void IBusIMEContext::OnCommitText(IBusInputContext *context, IBusText* text)
00244   {
00245     //nuxDebugMsg("***IBusIMEContext::OnCommitText::%s***", text->text);
00246     nuxAssert(context_ == context);
00247 
00248     text_entry_->DeleteSelection();
00249 
00250     if (text->text)
00251     {
00252       int cursor = text_entry_->cursor_;
00253       std::string new_text(text_entry_->GetText());
00254       std::string commit_text (text->text);
00255       new_text.insert (cursor, commit_text);
00256 
00257       text_entry_->SetText(new_text.c_str());
00258       text_entry_->SetCursor(cursor + commit_text.length());
00259       UpdateCursorLocation();
00260     }
00261   }
00262 
00263   void IBusIMEContext::OnUpdatePreeditText(IBusInputContext* context, IBusText* text, guint cursor_pos, gboolean visible)
00264   {
00265     //nuxDebugMsg("***IBusIMEContext::OnUpdatePreeditText***");
00266     nuxAssert(context_ == context);
00267     nuxAssert(IBUS_IS_TEXT(text));
00268 
00269     if (text_entry_->preedit_.empty())
00270       UpdateCursorLocation();
00271 
00272     if (visible)
00273     {
00274       IBusAttrList* attrs = text->attrs;
00275       if (attrs)
00276       {
00277         PangoAttrList* preedit_attrs = pango_attr_list_new();
00278         int i = 0;
00279         while (1)
00280         {
00281           IBusAttribute* attr = ibus_attr_list_get(attrs, i++);
00282           PangoAttribute* pango_attr;
00283           if (!attr)
00284             break;
00285           switch (attr->type)
00286           {
00287              case IBUS_ATTR_TYPE_UNDERLINE:
00288               pango_attr = pango_attr_underline_new ((PangoUnderline)attr->value);
00289               break;
00290             case IBUS_ATTR_TYPE_FOREGROUND:
00291               pango_attr = pango_attr_foreground_new (
00292                             ((attr->value & 0xff0000) >> 8) | 0xff,
00293                             ((attr->value & 0x00ff00)) | 0xff,
00294                             ((attr->value & 0x0000ff) << 8) | 0xff);
00295               break;
00296             case IBUS_ATTR_TYPE_BACKGROUND:
00297               pango_attr = pango_attr_background_new (
00298                             ((attr->value & 0xff0000) >> 8) | 0xff,
00299                             ((attr->value & 0x00ff00)) | 0xff,
00300                             ((attr->value & 0x0000ff) << 8) | 0xff);
00301               break;
00302             default:
00303               continue;
00304           }
00305           pango_attr->start_index = g_utf8_offset_to_pointer (text->text, attr->start_index) - text->text;
00306           pango_attr->end_index = g_utf8_offset_to_pointer (text->text, attr->end_index) - text->text;
00307           pango_attr_list_insert (preedit_attrs, pango_attr);
00308         }
00309         if (text_entry_->preedit_attrs_)
00310         {
00311           pango_attr_list_unref(text_entry_->preedit_attrs_);
00312           text_entry_->preedit_attrs_ = NULL;
00313         }
00314         text_entry_->preedit_attrs_ = preedit_attrs;
00315       }
00316       if (text->text)
00317       {
00318         std::string preedit(text->text);
00319         text_entry_->preedit_ = preedit;
00320         text_entry_->preedit_cursor_ = preedit.length();
00321         text_entry_->QueueRefresh (true, true);
00322       }
00323     }
00324     else
00325     {
00326       OnHidePreeditText(context_);
00327     }
00328   }
00329 
00330   void IBusIMEContext::OnShowPreeditText(IBusInputContext *context)
00331   {
00332     //nuxDebugMsg("***IBusIMEContext::OnShowPreeditText***");
00333     nuxAssert(context_ == context);
00334   }
00335 
00336   void IBusIMEContext::OnHidePreeditText(IBusInputContext *context)
00337   {
00338     //nuxDebugMsg("***IBusIMEContext::OnHidePreeditText***");
00339     nuxAssert(context_ == context);
00340 
00341     text_entry_->ResetPreedit();
00342     text_entry_->QueueRefresh (true, true);
00343   }
00344 
00345   void IBusIMEContext::OnEnable(IBusInputContext *context)
00346   {
00347     //nuxDebugMsg("***IBusIMEContext::OnEnable***");
00348     nuxAssert(context_ == context);
00349 
00350     text_entry_->ime_active_ = true;
00351     text_entry_->text_changed.emit(text_entry_);
00352     UpdateCursorLocation();
00353   }
00354 
00355   void IBusIMEContext::OnDisable(IBusInputContext *context)
00356   {
00357     //nuxDebugMsg("***IBusIMEContext::OnDisable***");
00358     nuxAssert(context_ == context);
00359     text_entry_->ime_active_ = false;
00360     text_entry_->ResetPreedit();
00361     text_entry_->QueueRefresh (true, true);
00362   }
00363 
00364   void IBusIMEContext::OnDestroy(IBusInputContext *context)
00365   {
00366     //nuxDebugMsg("***IBusIMEContext::OnDestroy***");
00367     nuxAssert(context_ == context);
00368 
00369     g_object_unref(context_);
00370     context_ = NULL;
00371   }
00372 
00373   void IBusIMEContext::ProcessKeyEventDone(IBusInputContext *context, GAsyncResult* res, ProcessKeyEventData *data)
00374   {
00375     //nuxDebugMsg("***IBusIMEContext::ProcessKeyEventDone***");
00376       std::unique_ptr<ProcessKeyEventData> key_ev(data);
00377       nuxAssert(key_ev->context->context_ == context);
00378 
00379       GError *error = NULL;
00380       gboolean processed = ibus_input_context_process_key_event_async_finish (
00381                             context,
00382                             res,
00383                             &error);
00384 
00385       if (error)
00386       {
00387         g_warning ("Process Key Event failed: %s.", error->message);
00388         g_error_free (error);
00389       }
00390 
00391       if (!processed)
00392       {
00393         key_ev->context->text_entry_->ProcessKeyEvent(key_ev->event.type(),
00394                                                       key_ev->event.key_sym(),
00395                                                       key_ev->event.flags() | IBUS_IGNORED_MASK,
00396                                                       key_ev->event.character().c_str(),
00397                                                       0);
00398       }
00399   }
00400 
00401   std::vector<Event> IBusIMEContext::ParseIBusHotkeys(const gchar** keybindings)
00402   {
00403     std::vector<Event> hotkeys;
00404 
00405     for (int i = 0; keybindings && keybindings[i]; ++i)
00406     {
00407       gchar** binding = g_strsplit (keybindings[i], "+", -1);
00408 
00409       if (binding)
00410       {
00411         Event ev;
00412         ev.type = EVENT_KEY_DOWN;
00413 
00414         for (int j = 0; binding && binding[j]; ++j)
00415         {
00416           if (strcmp(binding[j], "Release") == 0)
00417           {
00418             ev.type = EVENT_KEY_UP;
00419             continue;
00420           }
00421 
00422           KeySym key = XStringToKeysym(binding[j]);
00423           bool is_modifier = false;
00424 
00425           if (key)
00426           {
00427             switch (key)
00428             {
00429               case XK_Caps_Lock:
00430               case XK_Shift_Lock:
00431               case XK_Num_Lock:
00432               case XK_Scroll_Lock:
00433                 is_modifier = true;
00434             }
00435           }
00436           else
00437           {
00438             // Checking if the current key is a generic modifier key
00439             key = XStringToKeysym((std::string(binding[j]) + "_L").c_str());
00440             is_modifier = (key != 0);
00441           }
00442 
00443           if (is_modifier)
00444           {
00445             switch (key)
00446             {
00447               case XK_Control_L:
00448               case XK_Control_R:
00449                 ev.key_modifiers |= KEY_MODIFIER_CTRL;
00450                 break;
00451               case XK_Shift_L:
00452               case XK_Shift_R:
00453               case XK_Shift_Lock:
00454                 ev.key_modifiers |= KEY_MODIFIER_SHIFT;
00455                 break;
00456               case XK_Alt_L:
00457               case XK_Alt_R:
00458                 ev.key_modifiers |= KEY_MODIFIER_ALT;
00459                 break;
00460               case XK_Super_L:
00461               case XK_Super_R:
00462                 ev.key_modifiers |= KEY_MODIFIER_SUPER;
00463               case XK_Caps_Lock:
00464                 ev.key_modifiers |= KEY_MODIFIER_CAPS_LOCK;
00465                 break;
00466               case XK_Num_Lock:
00467                 ev.key_modifiers |= KEY_MODIFIER_NUMLOCK;
00468                 break;
00469               case XK_Scroll_Lock:
00470                 ev.key_modifiers |= KEY_MODIFIER_SCROLLLOCK;
00471                 break;
00472               // FIXME add support to Hyper and Meta keys in nux::Event
00473             }
00474           }
00475 
00476           if (!is_modifier && key)
00477             ev.x11_keysym = key;
00478         }
00479 
00480         if (ev.x11_keysym)
00481         {
00482           hotkeys.push_back(ev);
00483         }
00484 
00485         g_strfreev(binding);
00486       }
00487     }
00488 
00489     return hotkeys;
00490   }
00491 
00492   void IBusIMEContext::UpdateHotkeys()
00493   {
00494     IBusConfig* conf = ibus_bus_get_config(bus_);
00495     GVariant* val = ibus_config_get_value(conf, "general", "hotkey/trigger");
00496     const gchar** keybindings = g_variant_get_strv(val, NULL);
00497 
00498     hotkeys_ = ParseIBusHotkeys(keybindings);
00499 
00500     g_variant_unref(val);
00501   }
00502 
00503   bool IBusIMEContext::IsHotkeyEvent(EventType type, unsigned long keysym, unsigned long modifiers) const
00504   {
00505     for (Event const& ev : hotkeys_)
00506     {
00507       if (ev.x11_keysym == keysym && (ev.type == type || type == EVENT_KEY_DOWN))
00508       {
00509         if (ev.type == EVENT_KEY_UP)
00510           return (modifiers & ev.key_modifiers);
00511         else
00512           return (ev.key_modifiers == modifiers);
00513       }
00514     }
00515 
00516     return false;
00517   }
00518 }