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