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/event_types.h"
17 #include "base/message_loop/message_loop.h"
18 #include "base/strings/utf_string_conversions.h"
19 #include "ui/base/ime/composition_text.h"
20 #include "ui/base/ime/composition_text_util_pango.h"
21 #include "ui/base/ime/text_input_client.h"
22 #include "ui/events/event.h"
26 // Constructs a GdkEventKey from a XKeyEvent and returns it. Otherwise,
27 // returns NULL. The returned GdkEvent must be freed by gdk_event_free.
28 GdkEvent
* GdkEventFromXKeyEvent(XKeyEvent
& xkey
, bool is_modifier
) {
29 DCHECK(xkey
.type
== KeyPress
|| xkey
.type
== KeyRelease
);
32 GdkDisplay
* display
= gdk_x11_lookup_xdisplay(xkey
.display
);
34 // Fall back to the default display.
35 display
= gdk_display_get_default();
38 LOG(ERROR
) << "Cannot get a GdkDisplay for a key event.";
41 // Get a keysym and group.
42 KeySym keysym
= NoSymbol
;
43 guint8 keyboard_group
= 0;
44 XLookupString(&xkey
, NULL
, 0, &keysym
, NULL
);
45 GdkKeymap
* keymap
= gdk_keymap_get_for_display(display
);
46 GdkKeymapKey
* keys
= NULL
;
47 guint
* keyvals
= NULL
;
50 gdk_keymap_get_entries_for_keycode(keymap
, xkey
.keycode
,
51 &keys
, &keyvals
, &n_entries
)) {
52 for (gint i
= 0; i
< n_entries
; ++i
) {
53 if (keyvals
[i
] == keysym
) {
54 keyboard_group
= keys
[i
].group
;
64 GdkWindow
* window
= gdk_x11_window_lookup_for_display(display
, xkey
.window
);
68 window
= gdk_x11_window_foreign_new_for_display(display
, xkey
.window
);
70 LOG(ERROR
) << "Cannot get a GdkWindow for a key event.";
75 GdkEventType event_type
= xkey
.type
== KeyPress
?
76 GDK_KEY_PRESS
: GDK_KEY_RELEASE
;
77 GdkEvent
* event
= gdk_event_new(event_type
);
78 event
->key
.type
= event_type
;
79 event
->key
.window
= window
;
80 // GdkEventKey and XKeyEvent share the same definition for time and state.
81 event
->key
.send_event
= xkey
.send_event
;
82 event
->key
.time
= xkey
.time
;
83 event
->key
.state
= xkey
.state
;
84 event
->key
.keyval
= keysym
;
85 event
->key
.length
= 0;
86 event
->key
.string
= NULL
;
87 event
->key
.hardware_keycode
= xkey
.keycode
;
88 event
->key
.group
= keyboard_group
;
89 event
->key
.is_modifier
= is_modifier
;
97 X11InputMethodContextImplGtk2::X11InputMethodContextImplGtk2(
98 ui::LinuxInputMethodContextDelegate
* delegate
)
99 : delegate_(delegate
),
100 gtk_context_simple_(NULL
),
101 gtk_multicontext_(NULL
),
106 XModifierKeymap
* keymap
= XGetModifierMapping(
107 base::MessagePumpForUI::GetDefaultXDisplay());
108 for (int i
= 0; i
< 8 * keymap
->max_keypermod
; ++i
) {
109 if (keymap
->modifiermap
[i
])
110 modifier_keycodes_
.insert(keymap
->modifiermap
[i
]);
112 XFreeModifiermap(keymap
);
115 gtk_context_simple_
= gtk_im_context_simple_new();
116 gtk_multicontext_
= gtk_im_multicontext_new();
118 GtkIMContext
* contexts
[] = {gtk_context_simple_
, gtk_multicontext_
};
119 for (size_t i
= 0; i
< arraysize(contexts
); ++i
) {
120 g_signal_connect(contexts
[i
], "commit",
121 G_CALLBACK(OnCommitThunk
), this);
122 g_signal_connect(contexts
[i
], "preedit-changed",
123 G_CALLBACK(OnPreeditChangedThunk
), this);
124 g_signal_connect(contexts
[i
], "preedit-end",
125 G_CALLBACK(OnPreeditEndThunk
), this);
126 g_signal_connect(contexts
[i
], "preedit-start",
127 G_CALLBACK(OnPreeditStartThunk
), this);
128 // TODO(yukishiino): Handle operations on surrounding text.
129 // "delete-surrounding" and "retrieve-surrounding" signals should be
134 X11InputMethodContextImplGtk2::~X11InputMethodContextImplGtk2() {
136 if (gtk_context_simple_
) {
137 g_object_unref(gtk_context_simple_
);
138 gtk_context_simple_
= NULL
;
140 if (gtk_multicontext_
) {
141 g_object_unref(gtk_multicontext_
);
142 gtk_multicontext_
= NULL
;
146 // Overriden from ui::LinuxInputMethodContext
148 bool X11InputMethodContextImplGtk2::DispatchKeyEvent(
149 const ui::KeyEvent
& key_event
) {
150 if (!key_event
.HasNativeEvent())
153 // The caller must call Focus() first.
157 // Translate a XKeyEvent to a GdkEventKey.
158 const base::NativeEvent
& native_key_event
= key_event
.native_event();
159 GdkEvent
* event
= GdkEventFromXKeyEvent(
160 native_key_event
->xkey
,
161 IsKeycodeModifierKey(native_key_event
->xkey
.keycode
));
163 LOG(ERROR
) << "Cannot translate a XKeyEvent to a GdkEvent.";
167 // Set the client window and cursor location.
168 gtk_im_context_set_client_window(gtk_context_
, event
->key
.window
);
169 // Convert the last known caret bounds relative to the screen coordinates
170 // to a GdkRectangle relative to the client window.
173 gdk_window_get_origin(event
->key
.window
, &x
, &y
);
174 GdkRectangle rect
= {last_caret_bounds_
.x() - x
,
175 last_caret_bounds_
.y() - y
,
176 last_caret_bounds_
.width(),
177 last_caret_bounds_
.height()};
178 gtk_im_context_set_cursor_location(gtk_context_
, &rect
);
180 // Let an IME handle the key event.
181 commit_signal_trap_
.StartTrap(event
->key
.keyval
);
182 const gboolean handled
= gtk_im_context_filter_keypress(gtk_context_
,
184 commit_signal_trap_
.StopTrap();
185 gdk_event_free(event
);
187 return handled
&& !commit_signal_trap_
.IsSignalCaught();
190 void X11InputMethodContextImplGtk2::Reset() {
191 // Reset all the states of the context, not only preedit, caret but also
194 gtk_im_context_reset(gtk_context_simple_
);
195 gtk_im_context_reset(gtk_multicontext_
);
196 gtk_im_context_focus_out(gtk_context_simple_
);
197 gtk_im_context_focus_out(gtk_multicontext_
);
200 void X11InputMethodContextImplGtk2::OnTextInputTypeChanged(
201 ui::TextInputType text_input_type
) {
202 switch (text_input_type
) {
203 case ui::TEXT_INPUT_TYPE_NONE
:
204 case ui::TEXT_INPUT_TYPE_PASSWORD
:
205 gtk_context_
= gtk_context_simple_
;
208 gtk_context_
= gtk_multicontext_
;
210 gtk_im_context_focus_in(gtk_context_
);
213 void X11InputMethodContextImplGtk2::OnCaretBoundsChanged(
214 const gfx::Rect
& caret_bounds
) {
215 // Remember the caret bounds so that we can set the cursor location later.
216 // gtk_im_context_set_cursor_location() takes the location relative to the
217 // client window, which is unknown at this point. So we'll call
218 // gtk_im_context_set_cursor_location() later in ProcessKeyEvent() where
219 // (and only where) we know the client window.
220 last_caret_bounds_
= caret_bounds
;
225 bool X11InputMethodContextImplGtk2::IsKeycodeModifierKey(
226 unsigned int keycode
) const {
227 return modifier_keycodes_
.find(keycode
) != modifier_keycodes_
.end();
230 // GtkIMContext event handlers.
232 void X11InputMethodContextImplGtk2::OnCommit(GtkIMContext
* context
,
234 if (context
!= gtk_context_
)
237 const base::string16
& text_in_utf16
= base::UTF8ToUTF16(text
);
238 // If an underlying IME is emitting the "commit" signal to insert a character
239 // for a direct input key event, ignores the insertion of the character at
240 // this point, because we have to call DispatchKeyEventPostIME() for direct
241 // input key events. DispatchKeyEvent() takes care of the trapped character
242 // and calls DispatchKeyEventPostIME().
243 if (commit_signal_trap_
.Trap(text_in_utf16
))
246 delegate_
->OnCommit(text_in_utf16
);
249 void X11InputMethodContextImplGtk2::OnPreeditChanged(GtkIMContext
* context
) {
250 if (context
!= gtk_context_
)
254 PangoAttrList
* attrs
= NULL
;
256 gtk_im_context_get_preedit_string(context
, &str
, &attrs
, &cursor_pos
);
257 ui::CompositionText composition_text
;
258 ui::ExtractCompositionTextFromGtkPreedit(str
, attrs
, cursor_pos
,
261 pango_attr_list_unref(attrs
);
263 delegate_
->OnPreeditChanged(composition_text
);
266 void X11InputMethodContextImplGtk2::OnPreeditEnd(GtkIMContext
* context
) {
267 if (context
!= gtk_context_
)
270 delegate_
->OnPreeditEnd();
273 void X11InputMethodContextImplGtk2::OnPreeditStart(GtkIMContext
* context
) {
274 if (context
!= gtk_context_
)
277 delegate_
->OnPreeditStart();
280 // GtkCommitSignalTrap
282 X11InputMethodContextImplGtk2::GtkCommitSignalTrap::GtkCommitSignalTrap()
283 : is_trap_enabled_(false),
284 gdk_event_key_keyval_(GDK_KEY_VoidSymbol
),
285 is_signal_caught_(false) {}
287 void X11InputMethodContextImplGtk2::GtkCommitSignalTrap::StartTrap(
289 is_signal_caught_
= false;
290 gdk_event_key_keyval_
= keyval
;
291 is_trap_enabled_
= true;
294 void X11InputMethodContextImplGtk2::GtkCommitSignalTrap::StopTrap() {
295 is_trap_enabled_
= false;
298 bool X11InputMethodContextImplGtk2::GtkCommitSignalTrap::Trap(
299 const base::string16
& text
) {
300 DCHECK(!is_signal_caught_
);
301 if (is_trap_enabled_
&&
302 text
.length() == 1 &&
303 text
[0] == gdk_keyval_to_unicode(gdk_event_key_keyval_
)) {
304 is_signal_caught_
= true;
311 } // namespace libgtk2ui