| 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/libgtk2ui/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 libgtk2ui { | |
| 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 libgtk2ui | |
| OLD | NEW |