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
25namespace libgtk2ui {
26
27X11InputMethodContextImplGtk2::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
57X11InputMethodContextImplGtk2::~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
71bool 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
113void 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
124void 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
137void 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
149void 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
193GdkEvent* 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
278bool X11InputMethodContextImplGtk2::IsKeycodeModifierKey(
279    unsigned int keycode) const {
280  return modifier_keycodes_.find(keycode) != modifier_keycodes_.end();
281}
282
283bool 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
299void 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
316void 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
333void X11InputMethodContextImplGtk2::OnPreeditEnd(GtkIMContext* context) {
334  if (context != gtk_context_)
335    return;
336
337  delegate_->OnPreeditEnd();
338}
339
340void X11InputMethodContextImplGtk2::OnPreeditStart(GtkIMContext* context) {
341  if (context != gtk_context_)
342    return;
343
344  delegate_->OnPreeditStart();
345}
346
347// GtkCommitSignalTrap
348
349X11InputMethodContextImplGtk2::GtkCommitSignalTrap::GtkCommitSignalTrap()
350    : is_trap_enabled_(false),
351      gdk_event_key_keyval_(GDK_KEY_VoidSymbol),
352      is_signal_caught_(false) {}
353
354void X11InputMethodContextImplGtk2::GtkCommitSignalTrap::StartTrap(
355    guint keyval) {
356  is_signal_caught_ = false;
357  gdk_event_key_keyval_ = keyval;
358  is_trap_enabled_ = true;
359}
360
361void X11InputMethodContextImplGtk2::GtkCommitSignalTrap::StopTrap() {
362  is_trap_enabled_ = false;
363}
364
365bool 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