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
,
30 : delegate_(delegate
),
32 gdk_last_set_client_window_(NULL
) {
35 ResetXModifierKeycodesCache();
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
),
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
52 X11InputMethodContextImplGtk2::~X11InputMethodContextImplGtk2() {
54 g_object_unref(gtk_context_
);
59 // Overriden from ui::LinuxInputMethodContext
61 bool X11InputMethodContextImplGtk2::DispatchKeyEvent(
62 const ui::KeyEvent
& key_event
) {
63 if (!key_event
.HasNativeEvent() || !gtk_context_
)
66 // Translate a XKeyEvent to a GdkEventKey.
67 GdkEvent
* event
= GdkEventFromNativeEvent(key_event
.native_event());
69 LOG(ERROR
) << "Cannot translate a XKeyEvent to a GdkEvent.";
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.
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
);
90 gtk_im_context_filter_keypress(gtk_context_
, &event
->key
);
91 gdk_event_free(event
);
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
;
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
));
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
];
139 modifier_keycodes_
.insert(keycode
);
143 for (int j
= 0; j
< keysyms_per_keycode
; ++j
) {
144 switch (keysyms
[(keycode
- min_keycode
) * keysyms_per_keycode
+ j
]) {
147 meta_keycodes_
.push_back(keycode
);
151 super_keycodes_
.push_back(keycode
);
155 hyper_keycodes_
.push_back(keycode
);
162 GdkEvent
* X11InputMethodContextImplGtk2::GdkEventFromNativeEvent(
163 const base::NativeEvent
& native_event
) {
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
);
170 DCHECK(native_event
->type
== KeyPress
|| native_event
->type
== KeyRelease
);
171 xkeyevent
.xkey
= native_event
->xkey
;
173 XKeyEvent
& xkey
= xkeyevent
.xkey
;
176 GdkDisplay
* display
= gdk_x11_lookup_xdisplay(xkey
.display
);
178 // Fall back to the default display.
179 display
= gdk_display_get_default();
182 LOG(ERROR
) << "Cannot get a GdkDisplay for a key event.";
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
;
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
;
208 #if GTK_CHECK_VERSION(2,24,0)
209 GdkWindow
* window
= gdk_x11_window_lookup_for_display(display
, xkey
.window
);
211 GdkWindow
* window
= gdk_window_lookup_for_display(display
, xkey
.window
);
214 g_object_ref(window
);
216 #if GTK_CHECK_VERSION(2,24,0)
217 window
= gdk_x11_window_foreign_new_for_display(display
, xkey
.window
);
219 window
= gdk_window_foreign_new_for_display(display
, xkey
.window
);
222 LOG(ERROR
) << "Cannot get a GdkWindow for a key event.";
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
;
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
,
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
)
268 if (keybits
[keycode
/ 8] & 1 << (keycode
% 8))
274 // GtkIMContext event handlers.
276 void X11InputMethodContextImplGtk2::OnCommit(GtkIMContext
* context
,
278 if (context
!= gtk_context_
)
281 delegate_
->OnCommit(base::UTF8ToUTF16(text
));
284 void X11InputMethodContextImplGtk2::OnPreeditChanged(GtkIMContext
* context
) {
285 if (context
!= gtk_context_
)
289 PangoAttrList
* attrs
= NULL
;
291 gtk_im_context_get_preedit_string(context
, &str
, &attrs
, &cursor_pos
);
292 ui::CompositionText composition_text
;
293 ui::ExtractCompositionTextFromGtkPreedit(str
, attrs
, cursor_pos
,
296 pango_attr_list_unref(attrs
);
298 delegate_
->OnPreeditChanged(composition_text
);
301 void X11InputMethodContextImplGtk2::OnPreeditEnd(GtkIMContext
* context
) {
302 if (context
!= gtk_context_
)
305 delegate_
->OnPreeditEnd();
308 void X11InputMethodContextImplGtk2::OnPreeditStart(GtkIMContext
* context
) {
309 if (context
!= gtk_context_
)
312 delegate_
->OnPreeditStart();
315 } // namespace libgtk2ui