Popular sites on the NTP: check that experiment group StartsWith (rather than IS...
[chromium-blink-merge.git] / chrome / browser / ui / libgtk2ui / x11_input_method_context_impl_gtk2.cc
blob9ea4aba30e8be2bf6e28db00380a2121048456f1
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.
5 #include "chrome/browser/ui/libgtk2ui/x11_input_method_context_impl_gtk2.h"
7 #include <gdk/gdk.h>
8 #include <gdk/gdkkeysyms.h>
9 #include <gdk/gdkx.h>
11 #include <gtk/gtk.h>
13 #include <X11/X.h>
14 #include <X11/Xlib.h>
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"
25 namespace libgtk2ui {
27 X11InputMethodContextImplGtk2::X11InputMethodContextImplGtk2(
28 ui::LinuxInputMethodContextDelegate* delegate,
29 bool is_simple)
30 : delegate_(delegate),
31 gtk_context_(NULL),
32 gdk_last_set_client_window_(NULL) {
33 CHECK(delegate_);
35 ResetXModifierKeycodesCache();
37 gtk_context_ =
38 is_simple ? gtk_im_context_simple_new() : gtk_im_multicontext_new();
40 g_signal_connect(gtk_context_, "commit", G_CALLBACK(OnCommitThunk), this);
41 g_signal_connect(gtk_context_, "preedit-changed",
42 G_CALLBACK(OnPreeditChangedThunk), this);
43 g_signal_connect(gtk_context_, "preedit-end", G_CALLBACK(OnPreeditEndThunk),
44 this);
45 g_signal_connect(gtk_context_, "preedit-start",
46 G_CALLBACK(OnPreeditStartThunk), this);
47 // TODO(shuchen): Handle operations on surrounding text.
48 // "delete-surrounding" and "retrieve-surrounding" signals should be
49 // handled.
52 X11InputMethodContextImplGtk2::~X11InputMethodContextImplGtk2() {
53 if (gtk_context_) {
54 g_object_unref(gtk_context_);
55 gtk_context_ = NULL;
59 // Overriden from ui::LinuxInputMethodContext
61 bool X11InputMethodContextImplGtk2::DispatchKeyEvent(
62 const ui::KeyEvent& key_event) {
63 if (!key_event.HasNativeEvent() || !gtk_context_)
64 return false;
66 // Translate a XKeyEvent to a GdkEventKey.
67 GdkEvent* event = GdkEventFromNativeEvent(key_event.native_event());
68 if (!event) {
69 LOG(ERROR) << "Cannot translate a XKeyEvent to a GdkEvent.";
70 return false;
73 if (event->key.window != gdk_last_set_client_window_) {
74 gtk_im_context_set_client_window(gtk_context_, event->key.window);
75 gdk_last_set_client_window_ = event->key.window;
78 // Convert the last known caret bounds relative to the screen coordinates
79 // to a GdkRectangle relative to the client window.
80 gint x = 0;
81 gint y = 0;
82 gdk_window_get_origin(event->key.window, &x, &y);
83 GdkRectangle rect = {last_caret_bounds_.x() - x,
84 last_caret_bounds_.y() - y,
85 last_caret_bounds_.width(),
86 last_caret_bounds_.height()};
87 gtk_im_context_set_cursor_location(gtk_context_, &rect);
89 const bool handled =
90 gtk_im_context_filter_keypress(gtk_context_, &event->key);
91 gdk_event_free(event);
92 return handled;
95 void X11InputMethodContextImplGtk2::Reset() {
96 gtk_im_context_reset(gtk_context_);
99 void X11InputMethodContextImplGtk2::Focus() {
100 gtk_im_context_focus_in(gtk_context_);
103 void X11InputMethodContextImplGtk2::Blur() {
104 gtk_im_context_focus_out(gtk_context_);
107 void X11InputMethodContextImplGtk2::SetCursorLocation(const gfx::Rect& rect) {
108 // Remember the caret bounds so that we can set the cursor location later.
109 // gtk_im_context_set_cursor_location() takes the location relative to the
110 // client window, which is unknown at this point. So we'll call
111 // gtk_im_context_set_cursor_location() later in ProcessKeyEvent() where
112 // (and only where) we know the client window.
113 last_caret_bounds_ = rect;
116 // private:
118 void X11InputMethodContextImplGtk2::ResetXModifierKeycodesCache() {
119 modifier_keycodes_.clear();
120 meta_keycodes_.clear();
121 super_keycodes_.clear();
122 hyper_keycodes_.clear();
124 Display* display = gfx::GetXDisplay();
125 gfx::XScopedPtr<XModifierKeymap,
126 gfx::XObjectDeleter<XModifierKeymap, int, XFreeModifiermap>>
127 modmap(XGetModifierMapping(display));
128 int min_keycode = 0;
129 int max_keycode = 0;
130 int keysyms_per_keycode = 1;
131 XDisplayKeycodes(display, &min_keycode, &max_keycode);
132 gfx::XScopedPtr<KeySym[]> keysyms(
133 XGetKeyboardMapping(display, min_keycode, max_keycode - min_keycode + 1,
134 &keysyms_per_keycode));
135 for (int i = 0; i < 8 * modmap->max_keypermod; ++i) {
136 const int keycode = modmap->modifiermap[i];
137 if (!keycode)
138 continue;
139 modifier_keycodes_.insert(keycode);
141 if (!keysyms)
142 continue;
143 for (int j = 0; j < keysyms_per_keycode; ++j) {
144 switch (keysyms[(keycode - min_keycode) * keysyms_per_keycode + j]) {
145 case XK_Meta_L:
146 case XK_Meta_R:
147 meta_keycodes_.push_back(keycode);
148 break;
149 case XK_Super_L:
150 case XK_Super_R:
151 super_keycodes_.push_back(keycode);
152 break;
153 case XK_Hyper_L:
154 case XK_Hyper_R:
155 hyper_keycodes_.push_back(keycode);
156 break;
162 GdkEvent* X11InputMethodContextImplGtk2::GdkEventFromNativeEvent(
163 const base::NativeEvent& native_event) {
164 XEvent xkeyevent;
165 if (native_event->type == GenericEvent) {
166 // If this is an XI2 key event, build a matching core X event, to avoid
167 // having two cases for every use.
168 ui::InitXKeyEventFromXIDeviceEvent(*native_event, &xkeyevent);
169 } else {
170 DCHECK(native_event->type == KeyPress || native_event->type == KeyRelease);
171 xkeyevent.xkey = native_event->xkey;
173 XKeyEvent& xkey = xkeyevent.xkey;
175 // Get a GdkDisplay.
176 GdkDisplay* display = gdk_x11_lookup_xdisplay(xkey.display);
177 if (!display) {
178 // Fall back to the default display.
179 display = gdk_display_get_default();
181 if (!display) {
182 LOG(ERROR) << "Cannot get a GdkDisplay for a key event.";
183 return NULL;
185 // Get a keysym and group.
186 KeySym keysym = NoSymbol;
187 guint8 keyboard_group = 0;
188 XLookupString(&xkey, NULL, 0, &keysym, NULL);
189 GdkKeymap* keymap = gdk_keymap_get_for_display(display);
190 GdkKeymapKey* keys = NULL;
191 guint* keyvals = NULL;
192 gint n_entries = 0;
193 if (keymap &&
194 gdk_keymap_get_entries_for_keycode(keymap, xkey.keycode,
195 &keys, &keyvals, &n_entries)) {
196 for (gint i = 0; i < n_entries; ++i) {
197 if (keyvals[i] == keysym) {
198 keyboard_group = keys[i].group;
199 break;
203 g_free(keys);
204 keys = NULL;
205 g_free(keyvals);
206 keyvals = NULL;
207 // Get a GdkWindow.
208 #if GTK_CHECK_VERSION(2,24,0)
209 GdkWindow* window = gdk_x11_window_lookup_for_display(display, xkey.window);
210 #else
211 GdkWindow* window = gdk_window_lookup_for_display(display, xkey.window);
212 #endif
213 if (window)
214 g_object_ref(window);
215 else
216 #if GTK_CHECK_VERSION(2,24,0)
217 window = gdk_x11_window_foreign_new_for_display(display, xkey.window);
218 #else
219 window = gdk_window_foreign_new_for_display(display, xkey.window);
220 #endif
221 if (!window) {
222 LOG(ERROR) << "Cannot get a GdkWindow for a key event.";
223 return NULL;
226 // Create a GdkEvent.
227 GdkEventType event_type = xkey.type == KeyPress ?
228 GDK_KEY_PRESS : GDK_KEY_RELEASE;
229 GdkEvent* event = gdk_event_new(event_type);
230 event->key.type = event_type;
231 event->key.window = window;
232 // GdkEventKey and XKeyEvent share the same definition for time and state.
233 event->key.send_event = xkey.send_event;
234 event->key.time = xkey.time;
235 event->key.state = xkey.state;
236 event->key.keyval = keysym;
237 event->key.length = 0;
238 event->key.string = NULL;
239 event->key.hardware_keycode = xkey.keycode;
240 event->key.group = keyboard_group;
241 event->key.is_modifier = IsKeycodeModifierKey(xkey.keycode);
243 char keybits[32] = {0};
244 XQueryKeymap(xkey.display, keybits);
245 if (IsAnyOfKeycodesPressed(meta_keycodes_, keybits, sizeof keybits * 8))
246 event->key.state |= GDK_META_MASK;
247 if (IsAnyOfKeycodesPressed(super_keycodes_, keybits, sizeof keybits * 8))
248 event->key.state |= GDK_SUPER_MASK;
249 if (IsAnyOfKeycodesPressed(hyper_keycodes_, keybits, sizeof keybits * 8))
250 event->key.state |= GDK_HYPER_MASK;
252 return event;
255 bool X11InputMethodContextImplGtk2::IsKeycodeModifierKey(
256 unsigned int keycode) const {
257 return modifier_keycodes_.find(keycode) != modifier_keycodes_.end();
260 bool X11InputMethodContextImplGtk2::IsAnyOfKeycodesPressed(
261 const std::vector<int>& keycodes,
262 const char* keybits,
263 int num_keys) const {
264 for (size_t i = 0; i < keycodes.size(); ++i) {
265 const int keycode = keycodes[i];
266 if (keycode < 0 || num_keys <= keycode)
267 continue;
268 if (keybits[keycode / 8] & 1 << (keycode % 8))
269 return true;
271 return false;
274 // GtkIMContext event handlers.
276 void X11InputMethodContextImplGtk2::OnCommit(GtkIMContext* context,
277 gchar* text) {
278 if (context != gtk_context_)
279 return;
281 delegate_->OnCommit(base::UTF8ToUTF16(text));
284 void X11InputMethodContextImplGtk2::OnPreeditChanged(GtkIMContext* context) {
285 if (context != gtk_context_)
286 return;
288 gchar* str = NULL;
289 PangoAttrList* attrs = NULL;
290 gint cursor_pos = 0;
291 gtk_im_context_get_preedit_string(context, &str, &attrs, &cursor_pos);
292 ui::CompositionText composition_text;
293 ui::ExtractCompositionTextFromGtkPreedit(str, attrs, cursor_pos,
294 &composition_text);
295 g_free(str);
296 pango_attr_list_unref(attrs);
298 delegate_->OnPreeditChanged(composition_text);
301 void X11InputMethodContextImplGtk2::OnPreeditEnd(GtkIMContext* context) {
302 if (context != gtk_context_)
303 return;
305 delegate_->OnPreeditEnd();
308 void X11InputMethodContextImplGtk2::OnPreeditStart(GtkIMContext* context) {
309 if (context != gtk_context_)
310 return;
312 delegate_->OnPreeditStart();
315 } // namespace libgtk2ui