blob: cfd6edec9c42abe451ebd15e10c3a89c281c5f90 [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/libgtkui/x11_input_method_context_impl_gtk.h"
#include <gdk/gdk.h>
#include <gdk/gdkkeysyms.h>
#include <gdk/gdkx.h>
#include <stddef.h>
#include <gtk/gtk.h>
#include <X11/X.h>
#include <X11/Xlib.h>
#include "base/message_loop/message_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "ui/base/ime/composition_text.h"
#include "ui/base/ime/composition_text_util_pango.h"
#include "ui/base/ime/text_input_client.h"
#include "ui/events/event.h"
#include "ui/events/keycodes/keyboard_code_conversion_x.h"
#include "ui/gfx/geometry/dip_util.h"
#include "ui/gfx/x/x11_types.h"
#include "ui/views/linux_ui/linux_ui.h"
namespace libgtkui {
X11InputMethodContextImplGtk2::X11InputMethodContextImplGtk2(
ui::LinuxInputMethodContextDelegate* delegate,
bool is_simple)
: delegate_(delegate),
gtk_context_(NULL),
gdk_last_set_client_window_(NULL) {
CHECK(delegate_);
ResetXModifierKeycodesCache();
gtk_context_ =
is_simple ? gtk_im_context_simple_new() : gtk_im_multicontext_new();
g_signal_connect(gtk_context_, "commit", G_CALLBACK(OnCommitThunk), this);
g_signal_connect(gtk_context_, "preedit-changed",
G_CALLBACK(OnPreeditChangedThunk), this);
g_signal_connect(gtk_context_, "preedit-end", G_CALLBACK(OnPreeditEndThunk),
this);
g_signal_connect(gtk_context_, "preedit-start",
G_CALLBACK(OnPreeditStartThunk), this);
// TODO(shuchen): Handle operations on surrounding text.
// "delete-surrounding" and "retrieve-surrounding" signals should be
// handled.
}
X11InputMethodContextImplGtk2::~X11InputMethodContextImplGtk2() {
if (gtk_context_) {
g_object_unref(gtk_context_);
gtk_context_ = NULL;
}
}
// Overriden from ui::LinuxInputMethodContext
bool X11InputMethodContextImplGtk2::DispatchKeyEvent(
const ui::KeyEvent& key_event) {
if (!key_event.HasNativeEvent() || !gtk_context_)
return false;
// Translate a XKeyEvent to a GdkEventKey.
GdkEvent* event = GdkEventFromNativeEvent(key_event.native_event());
if (!event) {
LOG(ERROR) << "Cannot translate a XKeyEvent to a GdkEvent.";
return false;
}
if (event->key.window != gdk_last_set_client_window_) {
gtk_im_context_set_client_window(gtk_context_, event->key.window);
gdk_last_set_client_window_ = event->key.window;
}
// Convert the last known caret bounds relative to the screen coordinates
// to a GdkRectangle relative to the client window.
gint x = 0;
gint y = 0;
gdk_window_get_origin(event->key.window, &x, &y);
GdkRectangle gdk_rect = {
last_caret_bounds_.x() - x, last_caret_bounds_.y() - y,
last_caret_bounds_.width(), last_caret_bounds_.height()};
gtk_im_context_set_cursor_location(gtk_context_, &gdk_rect);
const bool handled =
gtk_im_context_filter_keypress(gtk_context_, &event->key);
gdk_event_free(event);
return handled;
}
void X11InputMethodContextImplGtk2::Reset() {
gtk_im_context_reset(gtk_context_);
}
void X11InputMethodContextImplGtk2::Focus() {
gtk_im_context_focus_in(gtk_context_);
}
void X11InputMethodContextImplGtk2::Blur() {
gtk_im_context_focus_out(gtk_context_);
}
void X11InputMethodContextImplGtk2::SetCursorLocation(const gfx::Rect& rect) {
// Remember the caret bounds so that we can set the cursor location later.
// gtk_im_context_set_cursor_location() takes the location relative to the
// client window, which is unknown at this point. So we'll call
// gtk_im_context_set_cursor_location() later in ProcessKeyEvent() where
// (and only where) we know the client window.
if (views::LinuxUI::instance()) {
last_caret_bounds_ = gfx::ConvertRectToPixel(
views::LinuxUI::instance()->GetDeviceScaleFactor(), rect);
} else {
last_caret_bounds_ = rect;
}
}
// private:
void X11InputMethodContextImplGtk2::ResetXModifierKeycodesCache() {
modifier_keycodes_.clear();
meta_keycodes_.clear();
super_keycodes_.clear();
hyper_keycodes_.clear();
Display* display = gfx::GetXDisplay();
gfx::XScopedPtr<XModifierKeymap,
gfx::XObjectDeleter<XModifierKeymap, int, XFreeModifiermap>>
modmap(XGetModifierMapping(display));
int min_keycode = 0;
int max_keycode = 0;
int keysyms_per_keycode = 1;
XDisplayKeycodes(display, &min_keycode, &max_keycode);
gfx::XScopedPtr<KeySym[]> keysyms(
XGetKeyboardMapping(display, min_keycode, max_keycode - min_keycode + 1,
&keysyms_per_keycode));
for (int i = 0; i < 8 * modmap->max_keypermod; ++i) {
const int keycode = modmap->modifiermap[i];
if (!keycode)
continue;
modifier_keycodes_.insert(keycode);
if (!keysyms)
continue;
for (int j = 0; j < keysyms_per_keycode; ++j) {
switch (keysyms[(keycode - min_keycode) * keysyms_per_keycode + j]) {
case XK_Meta_L:
case XK_Meta_R:
meta_keycodes_.push_back(keycode);
break;
case XK_Super_L:
case XK_Super_R:
super_keycodes_.push_back(keycode);
break;
case XK_Hyper_L:
case XK_Hyper_R:
hyper_keycodes_.push_back(keycode);
break;
}
}
}
}
GdkEvent* X11InputMethodContextImplGtk2::GdkEventFromNativeEvent(
const base::NativeEvent& native_event) {
XEvent xkeyevent;
if (native_event->type == GenericEvent) {
// If this is an XI2 key event, build a matching core X event, to avoid
// having two cases for every use.
ui::InitXKeyEventFromXIDeviceEvent(*native_event, &xkeyevent);
} else {
DCHECK(native_event->type == KeyPress || native_event->type == KeyRelease);
xkeyevent.xkey = native_event->xkey;
}
XKeyEvent& xkey = xkeyevent.xkey;
// Get a GdkDisplay.
GdkDisplay* display = gdk_x11_lookup_xdisplay(xkey.display);
if (!display) {
// Fall back to the default display.
display = gdk_display_get_default();
}
if (!display) {
LOG(ERROR) << "Cannot get a GdkDisplay for a key event.";
return NULL;
}
// Get a keysym and group.
KeySym keysym = NoSymbol;
guint8 keyboard_group = 0;
XLookupString(&xkey, NULL, 0, &keysym, NULL);
GdkKeymap* keymap = gdk_keymap_get_for_display(display);
GdkKeymapKey* keys = NULL;
guint* keyvals = NULL;
gint n_entries = 0;
if (keymap && gdk_keymap_get_entries_for_keycode(keymap, xkey.keycode, &keys,
&keyvals, &n_entries)) {
for (gint i = 0; i < n_entries; ++i) {
if (keyvals[i] == keysym) {
keyboard_group = keys[i].group;
break;
}
}
}
g_free(keys);
keys = NULL;
g_free(keyvals);
keyvals = NULL;
// Get a GdkWindow.
#if GTK_CHECK_VERSION(2, 24, 0)
GdkWindow* window = gdk_x11_window_lookup_for_display(display, xkey.window);
#else
GdkWindow* window = gdk_window_lookup_for_display(display, xkey.window);
#endif
if (window)
g_object_ref(window);
else
#if GTK_CHECK_VERSION(2, 24, 0)
window = gdk_x11_window_foreign_new_for_display(display, xkey.window);
#else
window = gdk_window_foreign_new_for_display(display, xkey.window);
#endif
if (!window) {
LOG(ERROR) << "Cannot get a GdkWindow for a key event.";
return NULL;
}
// Create a GdkEvent.
GdkEventType event_type =
xkey.type == KeyPress ? GDK_KEY_PRESS : GDK_KEY_RELEASE;
GdkEvent* event = gdk_event_new(event_type);
event->key.type = event_type;
event->key.window = window;
// GdkEventKey and XKeyEvent share the same definition for time and state.
event->key.send_event = xkey.send_event;
event->key.time = xkey.time;
event->key.state = xkey.state;
event->key.keyval = keysym;
event->key.length = 0;
event->key.string = NULL;
event->key.hardware_keycode = xkey.keycode;
event->key.group = keyboard_group;
event->key.is_modifier = IsKeycodeModifierKey(xkey.keycode);
char keybits[32] = {0};
XQueryKeymap(xkey.display, keybits);
if (IsAnyOfKeycodesPressed(meta_keycodes_, keybits, sizeof keybits * 8))
event->key.state |= GDK_META_MASK;
if (IsAnyOfKeycodesPressed(super_keycodes_, keybits, sizeof keybits * 8))
event->key.state |= GDK_SUPER_MASK;
if (IsAnyOfKeycodesPressed(hyper_keycodes_, keybits, sizeof keybits * 8))
event->key.state |= GDK_HYPER_MASK;
return event;
}
bool X11InputMethodContextImplGtk2::IsKeycodeModifierKey(
unsigned int keycode) const {
return modifier_keycodes_.find(keycode) != modifier_keycodes_.end();
}
bool X11InputMethodContextImplGtk2::IsAnyOfKeycodesPressed(
const std::vector<int>& keycodes,
const char* keybits,
int num_keys) const {
for (size_t i = 0; i < keycodes.size(); ++i) {
const int keycode = keycodes[i];
if (keycode < 0 || num_keys <= keycode)
continue;
if (keybits[keycode / 8] & 1 << (keycode % 8))
return true;
}
return false;
}
// GtkIMContext event handlers.
void X11InputMethodContextImplGtk2::OnCommit(GtkIMContext* context,
gchar* text) {
if (context != gtk_context_)
return;
delegate_->OnCommit(base::UTF8ToUTF16(text));
}
void X11InputMethodContextImplGtk2::OnPreeditChanged(GtkIMContext* context) {
if (context != gtk_context_)
return;
gchar* str = NULL;
PangoAttrList* attrs = NULL;
gint cursor_pos = 0;
gtk_im_context_get_preedit_string(context, &str, &attrs, &cursor_pos);
ui::CompositionText composition_text;
ui::ExtractCompositionTextFromGtkPreedit(str, attrs, cursor_pos,
&composition_text);
g_free(str);
pango_attr_list_unref(attrs);
delegate_->OnPreeditChanged(composition_text);
}
void X11InputMethodContextImplGtk2::OnPreeditEnd(GtkIMContext* context) {
if (context != gtk_context_)
return;
delegate_->OnPreeditEnd();
}
void X11InputMethodContextImplGtk2::OnPreeditStart(GtkIMContext* context) {
if (context != gtk_context_)
return;
delegate_->OnPreeditStart();
}
} // namespace libgtkui