Home | History | Annotate | Download | only in libgtk2ui
      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/ui/libgtk2ui/x11_input_method_context_impl_gtk2.h"
      6 
      7 #include <gdk/gdk.h>
      8 #include <gdk/gdkkeysyms.h>
      9 #include <gdk/gdkx.h>
     10 
     11 #include <gtk/gtk.h>
     12 
     13 #include <X11/X.h>
     14 #include <X11/Xlib.h>
     15 
     16 #include "base/message_loop/message_loop.h"
     17 #include "base/strings/utf_string_conversions.h"
     18 #include "ui/base/ime/composition_text.h"
     19 #include "ui/base/ime/composition_text_util_pango.h"
     20 #include "ui/base/ime/text_input_client.h"
     21 #include "ui/events/event.h"
     22 #include "ui/events/keycodes/keyboard_code_conversion_x.h"
     23 #include "ui/gfx/x/x11_types.h"
     24 
     25 namespace libgtk2ui {
     26 
     27 X11InputMethodContextImplGtk2::X11InputMethodContextImplGtk2(
     28     ui::LinuxInputMethodContextDelegate* delegate)
     29     : delegate_(delegate),
     30       gtk_context_simple_(NULL),
     31       gtk_multicontext_(NULL),
     32       gtk_context_(NULL),
     33       gdk_last_set_client_window_(NULL) {
     34   CHECK(delegate_);
     35 
     36   ResetXModifierKeycodesCache();
     37 
     38   gtk_context_simple_ = gtk_im_context_simple_new();
     39   gtk_multicontext_ = gtk_im_multicontext_new();
     40 
     41   GtkIMContext* contexts[] = {gtk_context_simple_, gtk_multicontext_};
     42   for (size_t i = 0; i < arraysize(contexts); ++i) {
     43     g_signal_connect(contexts[i], "commit",
     44                      G_CALLBACK(OnCommitThunk), this);
     45     g_signal_connect(contexts[i], "preedit-changed",
     46                      G_CALLBACK(OnPreeditChangedThunk), this);
     47     g_signal_connect(contexts[i], "preedit-end",
     48                      G_CALLBACK(OnPreeditEndThunk), this);
     49     g_signal_connect(contexts[i], "preedit-start",
     50                      G_CALLBACK(OnPreeditStartThunk), this);
     51     // TODO(yukishiino): Handle operations on surrounding text.
     52     // "delete-surrounding" and "retrieve-surrounding" signals should be
     53     // handled.
     54   }
     55 }
     56 
     57 X11InputMethodContextImplGtk2::~X11InputMethodContextImplGtk2() {
     58   gtk_context_ = NULL;
     59   if (gtk_context_simple_) {
     60     g_object_unref(gtk_context_simple_);
     61     gtk_context_simple_ = NULL;
     62   }
     63   if (gtk_multicontext_) {
     64     g_object_unref(gtk_multicontext_);
     65     gtk_multicontext_ = NULL;
     66   }
     67 }
     68 
     69 // Overriden from ui::LinuxInputMethodContext
     70 
     71 bool X11InputMethodContextImplGtk2::DispatchKeyEvent(
     72     const ui::KeyEvent& key_event) {
     73   if (!key_event.HasNativeEvent())
     74     return false;
     75 
     76   // The caller must call Focus() first.
     77   if (!gtk_context_)
     78     return false;
     79 
     80   // Translate a XKeyEvent to a GdkEventKey.
     81   GdkEvent* event = GdkEventFromNativeEvent(key_event.native_event());
     82   if (!event) {
     83     LOG(ERROR) << "Cannot translate a XKeyEvent to a GdkEvent.";
     84     return false;
     85   }
     86 
     87   // Set the client window and cursor location.
     88   if (event->key.window != gdk_last_set_client_window_) {
     89     gtk_im_context_set_client_window(gtk_context_, event->key.window);
     90     gdk_last_set_client_window_ = event->key.window;
     91   }
     92   // Convert the last known caret bounds relative to the screen coordinates
     93   // to a GdkRectangle relative to the client window.
     94   gint x = 0;
     95   gint y = 0;
     96   gdk_window_get_origin(event->key.window, &x, &y);
     97   GdkRectangle rect = {last_caret_bounds_.x() - x,
     98                        last_caret_bounds_.y() - y,
     99                        last_caret_bounds_.width(),
    100                        last_caret_bounds_.height()};
    101   gtk_im_context_set_cursor_location(gtk_context_, &rect);
    102 
    103   // Let an IME handle the key event.
    104   commit_signal_trap_.StartTrap(event->key.keyval);
    105   const gboolean handled = gtk_im_context_filter_keypress(gtk_context_,
    106                                                           &event->key);
    107   commit_signal_trap_.StopTrap();
    108   gdk_event_free(event);
    109 
    110   return handled && !commit_signal_trap_.IsSignalCaught();
    111 }
    112 
    113 void X11InputMethodContextImplGtk2::Reset() {
    114   // Reset all the states of the context, not only preedit, caret but also
    115   // focus.
    116   gtk_context_ = NULL;
    117   gtk_im_context_reset(gtk_context_simple_);
    118   gtk_im_context_reset(gtk_multicontext_);
    119   gtk_im_context_focus_out(gtk_context_simple_);
    120   gtk_im_context_focus_out(gtk_multicontext_);
    121   gdk_last_set_client_window_ = NULL;
    122 }
    123 
    124 void X11InputMethodContextImplGtk2::OnTextInputTypeChanged(
    125     ui::TextInputType text_input_type) {
    126   switch (text_input_type) {
    127     case ui::TEXT_INPUT_TYPE_NONE:
    128     case ui::TEXT_INPUT_TYPE_PASSWORD:
    129       gtk_context_ = gtk_context_simple_;
    130       break;
    131     default:
    132       gtk_context_ = gtk_multicontext_;
    133   }
    134   gtk_im_context_focus_in(gtk_context_);
    135 }
    136 
    137 void X11InputMethodContextImplGtk2::OnCaretBoundsChanged(
    138     const gfx::Rect& caret_bounds) {
    139   // Remember the caret bounds so that we can set the cursor location later.
    140   // gtk_im_context_set_cursor_location() takes the location relative to the
    141   // client window, which is unknown at this point.  So we'll call
    142   // gtk_im_context_set_cursor_location() later in ProcessKeyEvent() where
    143   // (and only where) we know the client window.
    144   last_caret_bounds_ = caret_bounds;
    145 }
    146 
    147 // private:
    148 
    149 void X11InputMethodContextImplGtk2::ResetXModifierKeycodesCache() {
    150   modifier_keycodes_.clear();
    151   meta_keycodes_.clear();
    152   super_keycodes_.clear();
    153   hyper_keycodes_.clear();
    154 
    155   Display* display = gfx::GetXDisplay();
    156   const XModifierKeymap* modmap = XGetModifierMapping(display);
    157   int min_keycode = 0;
    158   int max_keycode = 0;
    159   int keysyms_per_keycode = 1;
    160   XDisplayKeycodes(display, &min_keycode, &max_keycode);
    161   const KeySym* keysyms = XGetKeyboardMapping(
    162       display, min_keycode, max_keycode - min_keycode + 1,
    163       &keysyms_per_keycode);
    164   for (int i = 0; i < 8 * modmap->max_keypermod; ++i) {
    165     const int keycode = modmap->modifiermap[i];
    166     if (!keycode)
    167       continue;
    168     modifier_keycodes_.insert(keycode);
    169 
    170     if (!keysyms)
    171       continue;
    172     for (int j = 0; j < keysyms_per_keycode; ++j) {
    173       switch (keysyms[(keycode - min_keycode) * keysyms_per_keycode + j]) {
    174         case XK_Meta_L:
    175         case XK_Meta_R:
    176           meta_keycodes_.push_back(keycode);
    177           break;
    178         case XK_Super_L:
    179         case XK_Super_R:
    180           super_keycodes_.push_back(keycode);
    181           break;
    182         case XK_Hyper_L:
    183         case XK_Hyper_R:
    184           hyper_keycodes_.push_back(keycode);
    185           break;
    186       }
    187     }
    188   }
    189   XFree(const_cast<KeySym*>(keysyms));
    190   XFreeModifiermap(const_cast<XModifierKeymap*>(modmap));
    191 }
    192 
    193 GdkEvent* X11InputMethodContextImplGtk2::GdkEventFromNativeEvent(
    194     const base::NativeEvent& native_event) {
    195   XEvent xkeyevent;
    196   if (native_event->type == GenericEvent) {
    197     // If this is an XI2 key event, build a matching core X event, to avoid
    198     // having two cases for every use.
    199     ui::InitXKeyEventFromXIDeviceEvent(*native_event, &xkeyevent);
    200   } else {
    201     DCHECK(native_event->type == KeyPress || native_event->type == KeyRelease);
    202     xkeyevent.xkey = native_event->xkey;
    203   }
    204   XKeyEvent& xkey = xkeyevent.xkey;
    205 
    206   // Get a GdkDisplay.
    207   GdkDisplay* display = gdk_x11_lookup_xdisplay(xkey.display);
    208   if (!display) {
    209     // Fall back to the default display.
    210     display = gdk_display_get_default();
    211   }
    212   if (!display) {
    213     LOG(ERROR) << "Cannot get a GdkDisplay for a key event.";
    214     return NULL;
    215   }
    216   // Get a keysym and group.
    217   KeySym keysym = NoSymbol;
    218   guint8 keyboard_group = 0;
    219   XLookupString(&xkey, NULL, 0, &keysym, NULL);
    220   GdkKeymap* keymap = gdk_keymap_get_for_display(display);
    221   GdkKeymapKey* keys = NULL;
    222   guint* keyvals = NULL;
    223   gint n_entries = 0;
    224   if (keymap &&
    225       gdk_keymap_get_entries_for_keycode(keymap, xkey.keycode,
    226                                          &keys, &keyvals, &n_entries)) {
    227     for (gint i = 0; i < n_entries; ++i) {
    228       if (keyvals[i] == keysym) {
    229         keyboard_group = keys[i].group;
    230         break;
    231       }
    232     }
    233   }
    234   g_free(keys);
    235   keys = NULL;
    236   g_free(keyvals);
    237   keyvals = NULL;
    238   // Get a GdkWindow.
    239   GdkWindow* window = gdk_x11_window_lookup_for_display(display, xkey.window);
    240   if (window)
    241     g_object_ref(window);
    242   else
    243     window = gdk_x11_window_foreign_new_for_display(display, xkey.window);
    244   if (!window) {
    245     LOG(ERROR) << "Cannot get a GdkWindow for a key event.";
    246     return NULL;
    247   }
    248 
    249   // Create a GdkEvent.
    250   GdkEventType event_type = xkey.type == KeyPress ?
    251                             GDK_KEY_PRESS : GDK_KEY_RELEASE;
    252   GdkEvent* event = gdk_event_new(event_type);
    253   event->key.type = event_type;
    254   event->key.window = window;
    255   // GdkEventKey and XKeyEvent share the same definition for time and state.
    256   event->key.send_event = xkey.send_event;
    257   event->key.time = xkey.time;
    258   event->key.state = xkey.state;
    259   event->key.keyval = keysym;
    260   event->key.length = 0;
    261   event->key.string = NULL;
    262   event->key.hardware_keycode = xkey.keycode;
    263   event->key.group = keyboard_group;
    264   event->key.is_modifier = IsKeycodeModifierKey(xkey.keycode);
    265 
    266   char keybits[32] = {0};
    267   XQueryKeymap(xkey.display, keybits);
    268   if (IsAnyOfKeycodesPressed(meta_keycodes_, keybits, sizeof keybits * 8))
    269     event->key.state |= GDK_META_MASK;
    270   if (IsAnyOfKeycodesPressed(super_keycodes_, keybits, sizeof keybits * 8))
    271     event->key.state |= GDK_SUPER_MASK;
    272   if (IsAnyOfKeycodesPressed(hyper_keycodes_, keybits, sizeof keybits * 8))
    273     event->key.state |= GDK_HYPER_MASK;
    274 
    275   return event;
    276 }
    277 
    278 bool X11InputMethodContextImplGtk2::IsKeycodeModifierKey(
    279     unsigned int keycode) const {
    280   return modifier_keycodes_.find(keycode) != modifier_keycodes_.end();
    281 }
    282 
    283 bool X11InputMethodContextImplGtk2::IsAnyOfKeycodesPressed(
    284     const std::vector<int>& keycodes,
    285     const char* keybits,
    286     int num_keys) const {
    287   for (size_t i = 0; i < keycodes.size(); ++i) {
    288     const int keycode = keycodes[i];
    289     if (keycode < 0 || num_keys <= keycode)
    290       continue;
    291     if (keybits[keycode / 8] & 1 << (keycode % 8))
    292       return true;
    293   }
    294   return false;
    295 }
    296 
    297 // GtkIMContext event handlers.
    298 
    299 void X11InputMethodContextImplGtk2::OnCommit(GtkIMContext* context,
    300                                              gchar* text) {
    301   if (context != gtk_context_)
    302     return;
    303 
    304   const base::string16& text_in_utf16 = base::UTF8ToUTF16(text);
    305   // If an underlying IME is emitting the "commit" signal to insert a character
    306   // for a direct input key event, ignores the insertion of the character at
    307   // this point, because we have to call DispatchKeyEventPostIME() for direct
    308   // input key events.  DispatchKeyEvent() takes care of the trapped character
    309   // and calls DispatchKeyEventPostIME().
    310   if (commit_signal_trap_.Trap(text_in_utf16))
    311     return;
    312 
    313   delegate_->OnCommit(text_in_utf16);
    314 }
    315 
    316 void X11InputMethodContextImplGtk2::OnPreeditChanged(GtkIMContext* context) {
    317   if (context != gtk_context_)
    318     return;
    319 
    320   gchar* str = NULL;
    321   PangoAttrList* attrs = NULL;
    322   gint cursor_pos = 0;
    323   gtk_im_context_get_preedit_string(context, &str, &attrs, &cursor_pos);
    324   ui::CompositionText composition_text;
    325   ui::ExtractCompositionTextFromGtkPreedit(str, attrs, cursor_pos,
    326                                            &composition_text);
    327   g_free(str);
    328   pango_attr_list_unref(attrs);
    329 
    330   delegate_->OnPreeditChanged(composition_text);
    331 }
    332 
    333 void X11InputMethodContextImplGtk2::OnPreeditEnd(GtkIMContext* context) {
    334   if (context != gtk_context_)
    335     return;
    336 
    337   delegate_->OnPreeditEnd();
    338 }
    339 
    340 void X11InputMethodContextImplGtk2::OnPreeditStart(GtkIMContext* context) {
    341   if (context != gtk_context_)
    342     return;
    343 
    344   delegate_->OnPreeditStart();
    345 }
    346 
    347 // GtkCommitSignalTrap
    348 
    349 X11InputMethodContextImplGtk2::GtkCommitSignalTrap::GtkCommitSignalTrap()
    350     : is_trap_enabled_(false),
    351       gdk_event_key_keyval_(GDK_KEY_VoidSymbol),
    352       is_signal_caught_(false) {}
    353 
    354 void X11InputMethodContextImplGtk2::GtkCommitSignalTrap::StartTrap(
    355     guint keyval) {
    356   is_signal_caught_ = false;
    357   gdk_event_key_keyval_ = keyval;
    358   is_trap_enabled_ = true;
    359 }
    360 
    361 void X11InputMethodContextImplGtk2::GtkCommitSignalTrap::StopTrap() {
    362   is_trap_enabled_ = false;
    363 }
    364 
    365 bool X11InputMethodContextImplGtk2::GtkCommitSignalTrap::Trap(
    366     const base::string16& text) {
    367   DCHECK(!is_signal_caught_);
    368   if (is_trap_enabled_ &&
    369       text.length() == 1 &&
    370       text[0] == gdk_keyval_to_unicode(gdk_event_key_keyval_)) {
    371     is_signal_caught_ = true;
    372     return true;
    373   } else {
    374     return false;
    375   }
    376 }
    377 
    378 }  // namespace libgtk2ui
    379