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 11#include <gtk/gtk.h> 12 13#include <X11/X.h> 14#include <X11/Xlib.h> 15 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" 24 25namespace libgtk2ui { 26 27X11InputMethodContextImplGtk2::X11InputMethodContextImplGtk2( 28 ui::LinuxInputMethodContextDelegate* delegate) 29 : delegate_(delegate), 30 gtk_context_simple_(NULL), 31 gtk_multicontext_(NULL), 32 gtk_context_(NULL), 33 gdk_last_set_client_window_(NULL) { 34 CHECK(delegate_); 35 36 ResetXModifierKeycodesCache(); 37 38 gtk_context_simple_ = gtk_im_context_simple_new(); 39 gtk_multicontext_ = gtk_im_multicontext_new(); 40 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 53 // handled. 54 } 55} 56 57X11InputMethodContextImplGtk2::~X11InputMethodContextImplGtk2() { 58 gtk_context_ = NULL; 59 if (gtk_context_simple_) { 60 g_object_unref(gtk_context_simple_); 61 gtk_context_simple_ = NULL; 62 } 63 if (gtk_multicontext_) { 64 g_object_unref(gtk_multicontext_); 65 gtk_multicontext_ = NULL; 66 } 67} 68 69// Overriden from ui::LinuxInputMethodContext 70 71bool X11InputMethodContextImplGtk2::DispatchKeyEvent( 72 const ui::KeyEvent& key_event) { 73 if (!key_event.HasNativeEvent()) 74 return false; 75 76 // The caller must call Focus() first. 77 if (!gtk_context_) 78 return false; 79 80 // Translate a XKeyEvent to a GdkEventKey. 81 GdkEvent* event = GdkEventFromNativeEvent(key_event.native_event()); 82 if (!event) { 83 LOG(ERROR) << "Cannot translate a XKeyEvent to a GdkEvent."; 84 return false; 85 } 86 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; 91 } 92 // Convert the last known caret bounds relative to the screen coordinates 93 // to a GdkRectangle relative to the client window. 94 gint x = 0; 95 gint y = 0; 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); 102 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_, 106 &event->key); 107 commit_signal_trap_.StopTrap(); 108 gdk_event_free(event); 109 110 return handled && !commit_signal_trap_.IsSignalCaught(); 111} 112 113void X11InputMethodContextImplGtk2::Reset() { 114 // Reset all the states of the context, not only preedit, caret but also 115 // focus. 116 gtk_context_ = NULL; 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; 122} 123 124void 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_; 130 break; 131 default: 132 gtk_context_ = gtk_multicontext_; 133 } 134 gtk_im_context_focus_in(gtk_context_); 135} 136 137void 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; 145} 146 147// private: 148 149void X11InputMethodContextImplGtk2::ResetXModifierKeycodesCache() { 150 modifier_keycodes_.clear(); 151 meta_keycodes_.clear(); 152 super_keycodes_.clear(); 153 hyper_keycodes_.clear(); 154 155 Display* display = gfx::GetXDisplay(); 156 const XModifierKeymap* modmap = XGetModifierMapping(display); 157 int min_keycode = 0; 158 int max_keycode = 0; 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]; 166 if (!keycode) 167 continue; 168 modifier_keycodes_.insert(keycode); 169 170 if (!keysyms) 171 continue; 172 for (int j = 0; j < keysyms_per_keycode; ++j) { 173 switch (keysyms[(keycode - min_keycode) * keysyms_per_keycode + j]) { 174 case XK_Meta_L: 175 case XK_Meta_R: 176 meta_keycodes_.push_back(keycode); 177 break; 178 case XK_Super_L: 179 case XK_Super_R: 180 super_keycodes_.push_back(keycode); 181 break; 182 case XK_Hyper_L: 183 case XK_Hyper_R: 184 hyper_keycodes_.push_back(keycode); 185 break; 186 } 187 } 188 } 189 XFree(const_cast<KeySym*>(keysyms)); 190 XFreeModifiermap(const_cast<XModifierKeymap*>(modmap)); 191} 192 193GdkEvent* X11InputMethodContextImplGtk2::GdkEventFromNativeEvent( 194 const base::NativeEvent& native_event) { 195 XEvent xkeyevent; 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); 200 } else { 201 DCHECK(native_event->type == KeyPress || native_event->type == KeyRelease); 202 xkeyevent.xkey = native_event->xkey; 203 } 204 XKeyEvent& xkey = xkeyevent.xkey; 205 206 // Get a GdkDisplay. 207 GdkDisplay* display = gdk_x11_lookup_xdisplay(xkey.display); 208 if (!display) { 209 // Fall back to the default display. 210 display = gdk_display_get_default(); 211 } 212 if (!display) { 213 LOG(ERROR) << "Cannot get a GdkDisplay for a key event."; 214 return NULL; 215 } 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; 223 gint n_entries = 0; 224 if (keymap && 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; 230 break; 231 } 232 } 233 } 234 g_free(keys); 235 keys = NULL; 236 g_free(keyvals); 237 keyvals = NULL; 238 // Get a GdkWindow. 239 GdkWindow* window = gdk_x11_window_lookup_for_display(display, xkey.window); 240 if (window) 241 g_object_ref(window); 242 else 243 window = gdk_x11_window_foreign_new_for_display(display, xkey.window); 244 if (!window) { 245 LOG(ERROR) << "Cannot get a GdkWindow for a key event."; 246 return NULL; 247 } 248 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); 265 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; 274 275 return event; 276} 277 278bool X11InputMethodContextImplGtk2::IsKeycodeModifierKey( 279 unsigned int keycode) const { 280 return modifier_keycodes_.find(keycode) != modifier_keycodes_.end(); 281} 282 283bool X11InputMethodContextImplGtk2::IsAnyOfKeycodesPressed( 284 const std::vector<int>& keycodes, 285 const char* keybits, 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) 290 continue; 291 if (keybits[keycode / 8] & 1 << (keycode % 8)) 292 return true; 293 } 294 return false; 295} 296 297// GtkIMContext event handlers. 298 299void X11InputMethodContextImplGtk2::OnCommit(GtkIMContext* context, 300 gchar* text) { 301 if (context != gtk_context_) 302 return; 303 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)) 311 return; 312 313 delegate_->OnCommit(text_in_utf16); 314} 315 316void X11InputMethodContextImplGtk2::OnPreeditChanged(GtkIMContext* context) { 317 if (context != gtk_context_) 318 return; 319 320 gchar* str = NULL; 321 PangoAttrList* attrs = NULL; 322 gint cursor_pos = 0; 323 gtk_im_context_get_preedit_string(context, &str, &attrs, &cursor_pos); 324 ui::CompositionText composition_text; 325 ui::ExtractCompositionTextFromGtkPreedit(str, attrs, cursor_pos, 326 &composition_text); 327 g_free(str); 328 pango_attr_list_unref(attrs); 329 330 delegate_->OnPreeditChanged(composition_text); 331} 332 333void X11InputMethodContextImplGtk2::OnPreeditEnd(GtkIMContext* context) { 334 if (context != gtk_context_) 335 return; 336 337 delegate_->OnPreeditEnd(); 338} 339 340void X11InputMethodContextImplGtk2::OnPreeditStart(GtkIMContext* context) { 341 if (context != gtk_context_) 342 return; 343 344 delegate_->OnPreeditStart(); 345} 346 347// GtkCommitSignalTrap 348 349X11InputMethodContextImplGtk2::GtkCommitSignalTrap::GtkCommitSignalTrap() 350 : is_trap_enabled_(false), 351 gdk_event_key_keyval_(GDK_KEY_VoidSymbol), 352 is_signal_caught_(false) {} 353 354void X11InputMethodContextImplGtk2::GtkCommitSignalTrap::StartTrap( 355 guint keyval) { 356 is_signal_caught_ = false; 357 gdk_event_key_keyval_ = keyval; 358 is_trap_enabled_ = true; 359} 360 361void X11InputMethodContextImplGtk2::GtkCommitSignalTrap::StopTrap() { 362 is_trap_enabled_ = false; 363} 364 365bool 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; 372 return true; 373 } else { 374 return false; 375 } 376} 377 378} // namespace libgtk2ui 379