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