Home | History | Annotate | Download | only in libgtk2ui
      1 // Copyright (c) 2012 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/gtk2_key_bindings_handler.h"
      6 
      7 #include <gdk/gdkkeysyms.h>
      8 #include <X11/Xlib.h>
      9 #include <X11/XKBlib.h>
     10 
     11 #include <string>
     12 
     13 #include "base/logging.h"
     14 #include "base/strings/string_util.h"
     15 #include "chrome/browser/ui/libgtk2ui/gtk2_util.h"
     16 #include "content/public/browser/native_web_keyboard_event.h"
     17 #include "ui/base/x/x11_util.h"
     18 #include "ui/events/event.h"
     19 
     20 using ui::TextEditCommandAuraLinux;
     21 
     22 // TODO(erg): Rewrite the old gtk_key_bindings_handler_unittest.cc and get them
     23 // in a state that links. This code was adapted from the content layer GTK
     24 // code, which had some simple unit tests. However, the changes in the public
     25 // interface basically meant the tests need to be rewritten; this imposes weird
     26 // linking requirements regarding GTK+ as we don't have a libgtk2ui_unittests
     27 // yet. http://crbug.com/358297.
     28 
     29 namespace libgtk2ui {
     30 
     31 Gtk2KeyBindingsHandler::Gtk2KeyBindingsHandler()
     32     : fake_window_(gtk_offscreen_window_new()),
     33       handler_(CreateNewHandler()),
     34       has_xkb_(false) {
     35   gtk_container_add(GTK_CONTAINER(fake_window_), handler_.get());
     36 
     37   int opcode, event, error;
     38   int major = XkbMajorVersion;
     39   int minor = XkbMinorVersion;
     40   has_xkb_ = XkbQueryExtension(gfx::GetXDisplay(), &opcode, &event, &error,
     41                                &major, &minor);
     42 }
     43 
     44 Gtk2KeyBindingsHandler::~Gtk2KeyBindingsHandler() {
     45   handler_.Destroy();
     46   gtk_widget_destroy(fake_window_);
     47 }
     48 
     49 bool Gtk2KeyBindingsHandler::MatchEvent(
     50     const ui::Event& event,
     51     std::vector<TextEditCommandAuraLinux>* edit_commands) {
     52   CHECK(event.IsKeyEvent());
     53 
     54   const ui::KeyEvent& key_event = static_cast<const ui::KeyEvent&>(event);
     55   if (key_event.is_char() || !key_event.native_event())
     56     return false;
     57 
     58   GdkEventKey gdk_event;
     59   BuildGdkEventKeyFromXEvent(key_event.native_event(), &gdk_event);
     60 
     61   edit_commands_.clear();
     62   // If this key event matches a predefined key binding, corresponding signal
     63   // will be emitted.
     64   gtk_bindings_activate_event(GTK_OBJECT(handler_.get()), &gdk_event);
     65 
     66   bool matched = !edit_commands_.empty();
     67   if (edit_commands)
     68     edit_commands->swap(edit_commands_);
     69   return matched;
     70 }
     71 
     72 GtkWidget* Gtk2KeyBindingsHandler::CreateNewHandler() {
     73   Handler* handler =
     74       static_cast<Handler*>(g_object_new(HandlerGetType(), NULL));
     75 
     76   handler->owner = this;
     77 
     78   // We don't need to show the |handler| object on screen, so set its size to
     79   // zero.
     80   gtk_widget_set_size_request(GTK_WIDGET(handler), 0, 0);
     81 
     82   // Prevents it from handling any events by itself.
     83   gtk_widget_set_sensitive(GTK_WIDGET(handler), FALSE);
     84   gtk_widget_set_events(GTK_WIDGET(handler), 0);
     85   gtk_widget_set_can_focus(GTK_WIDGET(handler), TRUE);
     86 
     87   return GTK_WIDGET(handler);
     88 }
     89 
     90 void Gtk2KeyBindingsHandler::EditCommandMatched(
     91     TextEditCommandAuraLinux::CommandId id,
     92     const std::string& value,
     93     bool extend_selection) {
     94   edit_commands_.push_back(TextEditCommandAuraLinux(id,
     95                                                     value,
     96                                                     extend_selection));
     97 }
     98 
     99 void Gtk2KeyBindingsHandler::BuildGdkEventKeyFromXEvent(
    100     const base::NativeEvent& xevent,
    101     GdkEventKey* gdk_event) {
    102   GdkKeymap *keymap = gdk_keymap_get_for_display(gdk_display_get_default());
    103   GdkModifierType consumed, state;
    104 
    105   gdk_event->type = xevent->xany.type == KeyPress ?
    106                     GDK_KEY_PRESS : GDK_KEY_RELEASE;
    107   gdk_event->time = xevent->xkey.time;
    108   gdk_event->state = static_cast<GdkModifierType>(xevent->xkey.state);
    109   gdk_event->hardware_keycode = xevent->xkey.keycode;
    110 
    111   if (has_xkb_) {
    112     gdk_event->group = XkbGroupForCoreState(xevent->xkey.state);
    113   } else {
    114     // The overwhelming majority of people will be using X servers that support
    115     // XKB. GDK has a fallback here that does some complicated stuff to detect
    116     // whether a modifier key affects the keybinding, but that should be
    117     // extremely rare.
    118     NOTIMPLEMENTED();
    119     gdk_event->group = 0;
    120   }
    121 
    122   gdk_event->keyval = GDK_VoidSymbol;
    123   gdk_keymap_translate_keyboard_state(
    124       keymap,
    125       gdk_event->hardware_keycode,
    126       static_cast<GdkModifierType>(gdk_event->state),
    127       gdk_event->group,
    128       &gdk_event->keyval,
    129       NULL, NULL, &consumed);
    130 
    131   state = static_cast<GdkModifierType>(gdk_event->state & ~consumed);
    132   gdk_keymap_add_virtual_modifiers(keymap, &state);
    133   gdk_event->state |= state;
    134 }
    135 
    136 void Gtk2KeyBindingsHandler::HandlerInit(Handler *self) {
    137   self->owner = NULL;
    138 }
    139 
    140 void Gtk2KeyBindingsHandler::HandlerClassInit(HandlerClass *klass) {
    141   GtkTextViewClass* text_view_class = GTK_TEXT_VIEW_CLASS(klass);
    142   GtkWidgetClass* widget_class = GTK_WIDGET_CLASS(klass);
    143 
    144   // Overrides all virtual methods related to editor key bindings.
    145   text_view_class->backspace = BackSpace;
    146   text_view_class->copy_clipboard = CopyClipboard;
    147   text_view_class->cut_clipboard = CutClipboard;
    148   text_view_class->delete_from_cursor = DeleteFromCursor;
    149   text_view_class->insert_at_cursor = InsertAtCursor;
    150   text_view_class->move_cursor = MoveCursor;
    151   text_view_class->paste_clipboard = PasteClipboard;
    152   text_view_class->set_anchor = SetAnchor;
    153   text_view_class->toggle_overwrite = ToggleOverwrite;
    154   widget_class->show_help = ShowHelp;
    155 
    156   // "move-focus", "move-viewport", "select-all" and "toggle-cursor-visible"
    157   // have no corresponding virtual methods. Since glib 2.18 (gtk 2.14),
    158   // g_signal_override_class_handler() is introduced to override a signal
    159   // handler.
    160   g_signal_override_class_handler("move-focus",
    161                                   G_TYPE_FROM_CLASS(klass),
    162                                   G_CALLBACK(MoveFocus));
    163 
    164   g_signal_override_class_handler("move-viewport",
    165                                   G_TYPE_FROM_CLASS(klass),
    166                                   G_CALLBACK(MoveViewport));
    167 
    168   g_signal_override_class_handler("select-all",
    169                                   G_TYPE_FROM_CLASS(klass),
    170                                   G_CALLBACK(SelectAll));
    171 
    172   g_signal_override_class_handler("toggle-cursor-visible",
    173                                   G_TYPE_FROM_CLASS(klass),
    174                                   G_CALLBACK(ToggleCursorVisible));
    175 }
    176 
    177 GType Gtk2KeyBindingsHandler::HandlerGetType() {
    178   static volatile gsize type_id_volatile = 0;
    179   if (g_once_init_enter(&type_id_volatile)) {
    180     GType type_id = g_type_register_static_simple(
    181         GTK_TYPE_TEXT_VIEW,
    182         g_intern_static_string("Gtk2KeyBindingsHandler"),
    183         sizeof(HandlerClass),
    184         reinterpret_cast<GClassInitFunc>(HandlerClassInit),
    185         sizeof(Handler),
    186         reinterpret_cast<GInstanceInitFunc>(HandlerInit),
    187         static_cast<GTypeFlags>(0));
    188     g_once_init_leave(&type_id_volatile, type_id);
    189   }
    190   return type_id_volatile;
    191 }
    192 
    193 Gtk2KeyBindingsHandler* Gtk2KeyBindingsHandler::GetHandlerOwner(
    194     GtkTextView* text_view) {
    195   Handler* handler = G_TYPE_CHECK_INSTANCE_CAST(
    196       text_view, HandlerGetType(), Handler);
    197   DCHECK(handler);
    198   return handler->owner;
    199 }
    200 
    201 void Gtk2KeyBindingsHandler::BackSpace(GtkTextView* text_view) {
    202   GetHandlerOwner(text_view)
    203       ->EditCommandMatched(
    204           TextEditCommandAuraLinux::DELETE_BACKWARD, std::string(), false);
    205 }
    206 
    207 void Gtk2KeyBindingsHandler::CopyClipboard(GtkTextView* text_view) {
    208   GetHandlerOwner(text_view)->EditCommandMatched(
    209       TextEditCommandAuraLinux::COPY, std::string(), false);
    210 }
    211 
    212 void Gtk2KeyBindingsHandler::CutClipboard(GtkTextView* text_view) {
    213   GetHandlerOwner(text_view)->EditCommandMatched(
    214       TextEditCommandAuraLinux::CUT, std::string(), false);
    215 }
    216 
    217 void Gtk2KeyBindingsHandler::DeleteFromCursor(
    218     GtkTextView* text_view, GtkDeleteType type, gint count) {
    219   if (!count)
    220     return;
    221 
    222   TextEditCommandAuraLinux::CommandId commands[2] = {
    223     TextEditCommandAuraLinux::INVALID_COMMAND,
    224     TextEditCommandAuraLinux::INVALID_COMMAND,
    225   };
    226   switch (type) {
    227     case GTK_DELETE_CHARS:
    228       commands[0] = (count > 0 ?
    229           TextEditCommandAuraLinux::DELETE_FORWARD :
    230           TextEditCommandAuraLinux::DELETE_BACKWARD);
    231       break;
    232     case GTK_DELETE_WORD_ENDS:
    233       commands[0] = (count > 0 ?
    234           TextEditCommandAuraLinux::DELETE_WORD_FORWARD :
    235           TextEditCommandAuraLinux::DELETE_WORD_BACKWARD);
    236       break;
    237     case GTK_DELETE_WORDS:
    238       if (count > 0) {
    239         commands[0] = TextEditCommandAuraLinux::MOVE_WORD_FORWARD;
    240         commands[1] = TextEditCommandAuraLinux::DELETE_WORD_BACKWARD;
    241       } else {
    242         commands[0] = TextEditCommandAuraLinux::MOVE_WORD_BACKWARD;
    243         commands[1] = TextEditCommandAuraLinux::DELETE_WORD_FORWARD;
    244       }
    245       break;
    246     case GTK_DELETE_DISPLAY_LINES:
    247       commands[0] = TextEditCommandAuraLinux::MOVE_TO_BEGINING_OF_LINE;
    248       commands[1] = TextEditCommandAuraLinux::DELETE_TO_END_OF_LINE;
    249       break;
    250     case GTK_DELETE_DISPLAY_LINE_ENDS:
    251       commands[0] = (count > 0 ?
    252           TextEditCommandAuraLinux::DELETE_TO_END_OF_LINE :
    253           TextEditCommandAuraLinux::DELETE_TO_BEGINING_OF_LINE);
    254       break;
    255     case GTK_DELETE_PARAGRAPH_ENDS:
    256       commands[0] = (count > 0 ?
    257           TextEditCommandAuraLinux::DELETE_TO_END_OF_PARAGRAPH :
    258           TextEditCommandAuraLinux::DELETE_TO_BEGINING_OF_PARAGRAPH);
    259       break;
    260     case GTK_DELETE_PARAGRAPHS:
    261       commands[0] =
    262           TextEditCommandAuraLinux::MOVE_TO_BEGINING_OF_PARAGRAPH;
    263       commands[1] =
    264           TextEditCommandAuraLinux::DELETE_TO_END_OF_PARAGRAPH;
    265       break;
    266     default:
    267       // GTK_DELETE_WHITESPACE has no corresponding editor command.
    268       return;
    269   }
    270 
    271   Gtk2KeyBindingsHandler* owner = GetHandlerOwner(text_view);
    272   if (count < 0)
    273     count = -count;
    274   for (; count > 0; --count) {
    275     for (size_t i = 0; i < arraysize(commands); ++i)
    276       if (commands[i] != TextEditCommandAuraLinux::INVALID_COMMAND)
    277         owner->EditCommandMatched(commands[i], std::string(), false);
    278   }
    279 }
    280 
    281 void Gtk2KeyBindingsHandler::InsertAtCursor(GtkTextView* text_view,
    282                                             const gchar* str) {
    283   if (str && *str)
    284     GetHandlerOwner(text_view)->EditCommandMatched(
    285         TextEditCommandAuraLinux::INSERT_TEXT, str, false);
    286 }
    287 
    288 void Gtk2KeyBindingsHandler::MoveCursor(
    289     GtkTextView* text_view, GtkMovementStep step, gint count,
    290     gboolean extend_selection) {
    291   if (!count)
    292     return;
    293 
    294   TextEditCommandAuraLinux::CommandId command;
    295   switch (step) {
    296     case GTK_MOVEMENT_LOGICAL_POSITIONS:
    297       command = (count > 0 ?
    298                  TextEditCommandAuraLinux::MOVE_FORWARD :
    299                  TextEditCommandAuraLinux::MOVE_BACKWARD);
    300       break;
    301     case GTK_MOVEMENT_VISUAL_POSITIONS:
    302       command = (count > 0 ?
    303                  TextEditCommandAuraLinux::MOVE_RIGHT :
    304                  TextEditCommandAuraLinux::MOVE_LEFT);
    305       break;
    306     case GTK_MOVEMENT_WORDS:
    307       command = (count > 0 ?
    308                  TextEditCommandAuraLinux::MOVE_WORD_RIGHT :
    309                  TextEditCommandAuraLinux::MOVE_WORD_LEFT);
    310       break;
    311     case GTK_MOVEMENT_DISPLAY_LINES:
    312       command = (count > 0 ?
    313                  TextEditCommandAuraLinux::MOVE_DOWN :
    314                  TextEditCommandAuraLinux::MOVE_UP);
    315       break;
    316     case GTK_MOVEMENT_DISPLAY_LINE_ENDS:
    317       command = (count > 0 ?
    318                  TextEditCommandAuraLinux::MOVE_TO_END_OF_LINE :
    319                  TextEditCommandAuraLinux::MOVE_TO_BEGINING_OF_LINE);
    320       break;
    321     case GTK_MOVEMENT_PARAGRAPH_ENDS:
    322       command = (count > 0 ?
    323                  TextEditCommandAuraLinux::MOVE_TO_END_OF_PARAGRAPH :
    324                  TextEditCommandAuraLinux::MOVE_TO_BEGINING_OF_PARAGRAPH);
    325       break;
    326     case GTK_MOVEMENT_PAGES:
    327       command = (count > 0 ? TextEditCommandAuraLinux::MOVE_PAGE_DOWN :
    328                  TextEditCommandAuraLinux::MOVE_PAGE_UP);
    329       break;
    330     case GTK_MOVEMENT_BUFFER_ENDS:
    331       command = (count > 0 ? TextEditCommandAuraLinux::MOVE_TO_END_OF_DOCUMENT :
    332                  TextEditCommandAuraLinux::MOVE_TO_BEGINING_OF_DOCUMENT);
    333       break;
    334     default:
    335       // GTK_MOVEMENT_PARAGRAPHS and GTK_MOVEMENT_HORIZONTAL_PAGES have
    336       // no corresponding editor commands.
    337       return;
    338   }
    339 
    340   Gtk2KeyBindingsHandler* owner = GetHandlerOwner(text_view);
    341   if (count < 0)
    342     count = -count;
    343   for (; count > 0; --count)
    344     owner->EditCommandMatched(command, std::string(), extend_selection);
    345 }
    346 
    347 void Gtk2KeyBindingsHandler::MoveViewport(
    348     GtkTextView* text_view, GtkScrollStep step, gint count) {
    349   // Not supported by webkit.
    350 }
    351 
    352 void Gtk2KeyBindingsHandler::PasteClipboard(GtkTextView* text_view) {
    353   GetHandlerOwner(text_view)->EditCommandMatched(
    354       TextEditCommandAuraLinux::PASTE, std::string(), false);
    355 }
    356 
    357 void Gtk2KeyBindingsHandler::SelectAll(GtkTextView* text_view,
    358                                        gboolean select) {
    359   if (select) {
    360     GetHandlerOwner(text_view)->EditCommandMatched(
    361         TextEditCommandAuraLinux::SELECT_ALL, std::string(), false);
    362   } else {
    363     GetHandlerOwner(text_view)->EditCommandMatched(
    364         TextEditCommandAuraLinux::UNSELECT, std::string(), false);
    365   }
    366 }
    367 
    368 void Gtk2KeyBindingsHandler::SetAnchor(GtkTextView* text_view) {
    369   GetHandlerOwner(text_view)->EditCommandMatched(
    370       TextEditCommandAuraLinux::SET_MARK, std::string(), false);
    371 }
    372 
    373 void Gtk2KeyBindingsHandler::ToggleCursorVisible(GtkTextView* text_view) {
    374   // Not supported by webkit.
    375 }
    376 
    377 void Gtk2KeyBindingsHandler::ToggleOverwrite(GtkTextView* text_view) {
    378   // Not supported by webkit.
    379 }
    380 
    381 gboolean Gtk2KeyBindingsHandler::ShowHelp(GtkWidget* widget,
    382                                           GtkWidgetHelpType arg1) {
    383   // Just for disabling the default handler.
    384   return FALSE;
    385 }
    386 
    387 void Gtk2KeyBindingsHandler::MoveFocus(GtkWidget* widget,
    388                                        GtkDirectionType arg1) {
    389   // Just for disabling the default handler.
    390 }
    391 
    392 }  // namespace libgtk2ui
    393