ozone: evdev: Sync caps lock LED state to evdev
[chromium-blink-merge.git] / chrome / browser / ui / libgtk2ui / x11_input_method_context_impl_gtk2.cc
blobb4549925acce79fe9efb8f28f1f75957faf0292f
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 : 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_);
36 ResetXModifierKeycodesCache();
38 gtk_context_simple_ = gtk_im_context_simple_new();
39 gtk_multicontext_ = gtk_im_multicontext_new();
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.
57 X11InputMethodContextImplGtk2::~X11InputMethodContextImplGtk2() {
58 gtk_context_ = NULL;
59 if (gtk_context_simple_) {
60 g_object_unref(gtk_context_simple_);
61 gtk_context_simple_ = NULL;
63 if (gtk_multicontext_) {
64 g_object_unref(gtk_multicontext_);
65 gtk_multicontext_ = NULL;
69 // Overriden from ui::LinuxInputMethodContext
71 bool X11InputMethodContextImplGtk2::DispatchKeyEvent(
72 const ui::KeyEvent& key_event) {
73 if (!key_event.HasNativeEvent())
74 return false;
76 // The caller must call Focus() first.
77 if (!gtk_context_)
78 return false;
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;
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;
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);
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);
110 return handled && !commit_signal_trap_.IsSignalCaught();
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;
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_;
134 gtk_im_context_focus_in(gtk_context_);
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;
147 // private:
149 void X11InputMethodContextImplGtk2::ResetXModifierKeycodesCache() {
150 modifier_keycodes_.clear();
151 meta_keycodes_.clear();
152 super_keycodes_.clear();
153 hyper_keycodes_.clear();
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);
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;
189 XFree(const_cast<KeySym*>(keysyms));
190 XFreeModifiermap(const_cast<XModifierKeymap*>(modmap));
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;
204 XKeyEvent& xkey = xkeyevent.xkey;
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();
212 if (!display) {
213 LOG(ERROR) << "Cannot get a GdkDisplay for a key event.";
214 return NULL;
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;
234 g_free(keys);
235 keys = NULL;
236 g_free(keyvals);
237 keyvals = NULL;
238 // Get a GdkWindow.
239 #if GTK_CHECK_VERSION(2,24,0)
240 GdkWindow* window = gdk_x11_window_lookup_for_display(display, xkey.window);
241 #else
242 GdkWindow* window = gdk_window_lookup_for_display(display, xkey.window);
243 #endif
244 if (window)
245 g_object_ref(window);
246 else
247 #if GTK_CHECK_VERSION(2,24,0)
248 window = gdk_x11_window_foreign_new_for_display(display, xkey.window);
249 #else
250 window = gdk_window_foreign_new_for_display(display, xkey.window);
251 #endif
252 if (!window) {
253 LOG(ERROR) << "Cannot get a GdkWindow for a key event.";
254 return NULL;
257 // Create a GdkEvent.
258 GdkEventType event_type = xkey.type == KeyPress ?
259 GDK_KEY_PRESS : GDK_KEY_RELEASE;
260 GdkEvent* event = gdk_event_new(event_type);
261 event->key.type = event_type;
262 event->key.window = window;
263 // GdkEventKey and XKeyEvent share the same definition for time and state.
264 event->key.send_event = xkey.send_event;
265 event->key.time = xkey.time;
266 event->key.state = xkey.state;
267 event->key.keyval = keysym;
268 event->key.length = 0;
269 event->key.string = NULL;
270 event->key.hardware_keycode = xkey.keycode;
271 event->key.group = keyboard_group;
272 event->key.is_modifier = IsKeycodeModifierKey(xkey.keycode);
274 char keybits[32] = {0};
275 XQueryKeymap(xkey.display, keybits);
276 if (IsAnyOfKeycodesPressed(meta_keycodes_, keybits, sizeof keybits * 8))
277 event->key.state |= GDK_META_MASK;
278 if (IsAnyOfKeycodesPressed(super_keycodes_, keybits, sizeof keybits * 8))
279 event->key.state |= GDK_SUPER_MASK;
280 if (IsAnyOfKeycodesPressed(hyper_keycodes_, keybits, sizeof keybits * 8))
281 event->key.state |= GDK_HYPER_MASK;
283 return event;
286 bool X11InputMethodContextImplGtk2::IsKeycodeModifierKey(
287 unsigned int keycode) const {
288 return modifier_keycodes_.find(keycode) != modifier_keycodes_.end();
291 bool X11InputMethodContextImplGtk2::IsAnyOfKeycodesPressed(
292 const std::vector<int>& keycodes,
293 const char* keybits,
294 int num_keys) const {
295 for (size_t i = 0; i < keycodes.size(); ++i) {
296 const int keycode = keycodes[i];
297 if (keycode < 0 || num_keys <= keycode)
298 continue;
299 if (keybits[keycode / 8] & 1 << (keycode % 8))
300 return true;
302 return false;
305 // GtkIMContext event handlers.
307 void X11InputMethodContextImplGtk2::OnCommit(GtkIMContext* context,
308 gchar* text) {
309 if (context != gtk_context_)
310 return;
312 const base::string16& text_in_utf16 = base::UTF8ToUTF16(text);
313 // If an underlying IME is emitting the "commit" signal to insert a character
314 // for a direct input key event, ignores the insertion of the character at
315 // this point, because we have to call DispatchKeyEventPostIME() for direct
316 // input key events. DispatchKeyEvent() takes care of the trapped character
317 // and calls DispatchKeyEventPostIME().
318 if (commit_signal_trap_.Trap(text_in_utf16))
319 return;
321 delegate_->OnCommit(text_in_utf16);
324 void X11InputMethodContextImplGtk2::OnPreeditChanged(GtkIMContext* context) {
325 if (context != gtk_context_)
326 return;
328 gchar* str = NULL;
329 PangoAttrList* attrs = NULL;
330 gint cursor_pos = 0;
331 gtk_im_context_get_preedit_string(context, &str, &attrs, &cursor_pos);
332 ui::CompositionText composition_text;
333 ui::ExtractCompositionTextFromGtkPreedit(str, attrs, cursor_pos,
334 &composition_text);
335 g_free(str);
336 pango_attr_list_unref(attrs);
338 delegate_->OnPreeditChanged(composition_text);
341 void X11InputMethodContextImplGtk2::OnPreeditEnd(GtkIMContext* context) {
342 if (context != gtk_context_)
343 return;
345 delegate_->OnPreeditEnd();
348 void X11InputMethodContextImplGtk2::OnPreeditStart(GtkIMContext* context) {
349 if (context != gtk_context_)
350 return;
352 delegate_->OnPreeditStart();
355 // GtkCommitSignalTrap
357 X11InputMethodContextImplGtk2::GtkCommitSignalTrap::GtkCommitSignalTrap()
358 : is_trap_enabled_(false),
359 #if GTK_CHECK_VERSION (2,22,0)
360 gdk_event_key_keyval_(GDK_KEY_VoidSymbol),
361 #else
362 gdk_event_key_keyval_(GDK_VoidSymbol),
363 #endif
364 is_signal_caught_(false) {}
366 void X11InputMethodContextImplGtk2::GtkCommitSignalTrap::StartTrap(
367 guint keyval) {
368 is_signal_caught_ = false;
369 gdk_event_key_keyval_ = keyval;
370 is_trap_enabled_ = true;
373 void X11InputMethodContextImplGtk2::GtkCommitSignalTrap::StopTrap() {
374 is_trap_enabled_ = false;
377 bool X11InputMethodContextImplGtk2::GtkCommitSignalTrap::Trap(
378 const base::string16& text) {
379 DCHECK(!is_signal_caught_);
380 if (is_trap_enabled_ &&
381 text.length() == 1 &&
382 text[0] == gdk_keyval_to_unicode(gdk_event_key_keyval_)) {
383 is_signal_caught_ = true;
384 return true;
385 } else {
386 return false;
390 } // namespace libgtk2ui