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"
8 #include <gdk/gdkkeysyms.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"
27 X11InputMethodContextImplGtk2::X11InputMethodContextImplGtk2(
28 ui::LinuxInputMethodContextDelegate
* delegate
)
29 : delegate_(delegate
),
30 gtk_context_simple_(NULL
),
31 gtk_multicontext_(NULL
),
33 gdk_last_set_client_window_(NULL
) {
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
57 X11InputMethodContextImplGtk2::~X11InputMethodContextImplGtk2() {
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())
76 // The caller must call Focus() first.
80 // Translate a XKeyEvent to a GdkEventKey.
81 GdkEvent
* event
= GdkEventFromNativeEvent(key_event
.native_event());
83 LOG(ERROR
) << "Cannot translate a XKeyEvent to a GdkEvent.";
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.
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_
,
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
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_
;
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
;
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
);
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
];
168 modifier_keycodes_
.insert(keycode
);
172 for (int j
= 0; j
< keysyms_per_keycode
; ++j
) {
173 switch (keysyms
[(keycode
- min_keycode
) * keysyms_per_keycode
+ j
]) {
176 meta_keycodes_
.push_back(keycode
);
180 super_keycodes_
.push_back(keycode
);
184 hyper_keycodes_
.push_back(keycode
);
189 XFree(const_cast<KeySym
*>(keysyms
));
190 XFreeModifiermap(const_cast<XModifierKeymap
*>(modmap
));
193 GdkEvent
* X11InputMethodContextImplGtk2::GdkEventFromNativeEvent(
194 const base::NativeEvent
& native_event
) {
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
);
201 DCHECK(native_event
->type
== KeyPress
|| native_event
->type
== KeyRelease
);
202 xkeyevent
.xkey
= native_event
->xkey
;
204 XKeyEvent
& xkey
= xkeyevent
.xkey
;
207 GdkDisplay
* display
= gdk_x11_lookup_xdisplay(xkey
.display
);
209 // Fall back to the default display.
210 display
= gdk_display_get_default();
213 LOG(ERROR
) << "Cannot get a GdkDisplay for a key event.";
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
;
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
;
239 GdkWindow
* window
= gdk_x11_window_lookup_for_display(display
, xkey
.window
);
241 g_object_ref(window
);
243 window
= gdk_x11_window_foreign_new_for_display(display
, xkey
.window
);
245 LOG(ERROR
) << "Cannot get a GdkWindow for a key event.";
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
);
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
;
278 bool X11InputMethodContextImplGtk2::IsKeycodeModifierKey(
279 unsigned int keycode
) const {
280 return modifier_keycodes_
.find(keycode
) != modifier_keycodes_
.end();
283 bool X11InputMethodContextImplGtk2::IsAnyOfKeycodesPressed(
284 const std::vector
<int>& keycodes
,
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
)
291 if (keybits
[keycode
/ 8] & 1 << (keycode
% 8))
297 // GtkIMContext event handlers.
299 void X11InputMethodContextImplGtk2::OnCommit(GtkIMContext
* context
,
301 if (context
!= gtk_context_
)
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
))
313 delegate_
->OnCommit(text_in_utf16
);
316 void X11InputMethodContextImplGtk2::OnPreeditChanged(GtkIMContext
* context
) {
317 if (context
!= gtk_context_
)
321 PangoAttrList
* attrs
= NULL
;
323 gtk_im_context_get_preedit_string(context
, &str
, &attrs
, &cursor_pos
);
324 ui::CompositionText composition_text
;
325 ui::ExtractCompositionTextFromGtkPreedit(str
, attrs
, cursor_pos
,
328 pango_attr_list_unref(attrs
);
330 delegate_
->OnPreeditChanged(composition_text
);
333 void X11InputMethodContextImplGtk2::OnPreeditEnd(GtkIMContext
* context
) {
334 if (context
!= gtk_context_
)
337 delegate_
->OnPreeditEnd();
340 void X11InputMethodContextImplGtk2::OnPreeditStart(GtkIMContext
* context
) {
341 if (context
!= gtk_context_
)
344 delegate_
->OnPreeditStart();
347 // GtkCommitSignalTrap
349 X11InputMethodContextImplGtk2::GtkCommitSignalTrap::GtkCommitSignalTrap()
350 : is_trap_enabled_(false),
351 gdk_event_key_keyval_(GDK_KEY_VoidSymbol
),
352 is_signal_caught_(false) {}
354 void X11InputMethodContextImplGtk2::GtkCommitSignalTrap::StartTrap(
356 is_signal_caught_
= false;
357 gdk_event_key_keyval_
= keyval
;
358 is_trap_enabled_
= true;
361 void X11InputMethodContextImplGtk2::GtkCommitSignalTrap::StopTrap() {
362 is_trap_enabled_
= false;
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;
378 } // namespace libgtk2ui