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.
5 #include "chrome/browser/ui/libgtk2ui/gtk2_key_bindings_handler.h"
7 #include <gdk/gdkkeysyms.h>
9 #include <X11/XKBlib.h>
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"
20 using ui::TextEditCommandAuraLinux
;
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.
31 Gtk2KeyBindingsHandler::Gtk2KeyBindingsHandler()
32 : fake_window_(gtk_offscreen_window_new()),
33 handler_(CreateNewHandler()),
35 gtk_container_add(GTK_CONTAINER(fake_window_
), handler_
.get());
37 int opcode
, event
, error
;
38 int major
= XkbMajorVersion
;
39 int minor
= XkbMinorVersion
;
40 has_xkb_
= XkbQueryExtension(gfx::GetXDisplay(), &opcode
, &event
, &error
,
44 Gtk2KeyBindingsHandler::~Gtk2KeyBindingsHandler() {
46 gtk_widget_destroy(fake_window_
);
49 bool Gtk2KeyBindingsHandler::MatchEvent(
50 const ui::Event
& event
,
51 std::vector
<TextEditCommandAuraLinux
>* edit_commands
) {
52 CHECK(event
.IsKeyEvent());
54 const ui::KeyEvent
& key_event
= static_cast<const ui::KeyEvent
&>(event
);
55 if (key_event
.is_char() || !key_event
.native_event())
58 GdkEventKey gdk_event
;
59 BuildGdkEventKeyFromXEvent(key_event
.native_event(), &gdk_event
);
61 edit_commands_
.clear();
62 // If this key event matches a predefined key binding, corresponding signal
64 gtk_bindings_activate_event(GTK_OBJECT(handler_
.get()), &gdk_event
);
66 bool matched
= !edit_commands_
.empty();
68 edit_commands
->swap(edit_commands_
);
72 GtkWidget
* Gtk2KeyBindingsHandler::CreateNewHandler() {
74 static_cast<Handler
*>(g_object_new(HandlerGetType(), NULL
));
76 handler
->owner
= this;
78 // We don't need to show the |handler| object on screen, so set its size to
80 gtk_widget_set_size_request(GTK_WIDGET(handler
), 0, 0);
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
);
87 return GTK_WIDGET(handler
);
90 void Gtk2KeyBindingsHandler::EditCommandMatched(
91 TextEditCommandAuraLinux::CommandId id
,
92 const std::string
& value
,
93 bool extend_selection
) {
94 edit_commands_
.push_back(TextEditCommandAuraLinux(id
,
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
;
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
;
112 gdk_event
->group
= XkbGroupForCoreState(xevent
->xkey
.state
);
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
118 static bool logged
= false;
123 gdk_event
->group
= 0;
126 gdk_event
->keyval
= GDK_VoidSymbol
;
127 gdk_keymap_translate_keyboard_state(
129 gdk_event
->hardware_keycode
,
130 static_cast<GdkModifierType
>(gdk_event
->state
),
133 NULL
, NULL
, &consumed
);
135 state
= static_cast<GdkModifierType
>(gdk_event
->state
& ~consumed
);
136 gdk_keymap_add_virtual_modifiers(keymap
, &state
);
137 gdk_event
->state
|= state
;
140 void Gtk2KeyBindingsHandler::HandlerInit(Handler
*self
) {
144 void Gtk2KeyBindingsHandler::HandlerClassInit(HandlerClass
*klass
) {
145 GtkTextViewClass
* text_view_class
= GTK_TEXT_VIEW_CLASS(klass
);
146 GtkWidgetClass
* widget_class
= GTK_WIDGET_CLASS(klass
);
148 // Overrides all virtual methods related to editor key bindings.
149 text_view_class
->backspace
= BackSpace
;
150 text_view_class
->copy_clipboard
= CopyClipboard
;
151 text_view_class
->cut_clipboard
= CutClipboard
;
152 text_view_class
->delete_from_cursor
= DeleteFromCursor
;
153 text_view_class
->insert_at_cursor
= InsertAtCursor
;
154 text_view_class
->move_cursor
= MoveCursor
;
155 text_view_class
->paste_clipboard
= PasteClipboard
;
156 text_view_class
->set_anchor
= SetAnchor
;
157 text_view_class
->toggle_overwrite
= ToggleOverwrite
;
158 widget_class
->show_help
= ShowHelp
;
160 // "move-focus", "move-viewport", "select-all" and "toggle-cursor-visible"
161 // have no corresponding virtual methods. Since glib 2.18 (gtk 2.14),
162 // g_signal_override_class_handler() is introduced to override a signal
164 g_signal_override_class_handler("move-focus",
165 G_TYPE_FROM_CLASS(klass
),
166 G_CALLBACK(MoveFocus
));
168 g_signal_override_class_handler("move-viewport",
169 G_TYPE_FROM_CLASS(klass
),
170 G_CALLBACK(MoveViewport
));
172 g_signal_override_class_handler("select-all",
173 G_TYPE_FROM_CLASS(klass
),
174 G_CALLBACK(SelectAll
));
176 g_signal_override_class_handler("toggle-cursor-visible",
177 G_TYPE_FROM_CLASS(klass
),
178 G_CALLBACK(ToggleCursorVisible
));
181 GType
Gtk2KeyBindingsHandler::HandlerGetType() {
182 static volatile gsize type_id_volatile
= 0;
183 if (g_once_init_enter(&type_id_volatile
)) {
184 GType type_id
= g_type_register_static_simple(
186 g_intern_static_string("Gtk2KeyBindingsHandler"),
187 sizeof(HandlerClass
),
188 reinterpret_cast<GClassInitFunc
>(HandlerClassInit
),
190 reinterpret_cast<GInstanceInitFunc
>(HandlerInit
),
191 static_cast<GTypeFlags
>(0));
192 g_once_init_leave(&type_id_volatile
, type_id
);
194 return type_id_volatile
;
197 Gtk2KeyBindingsHandler
* Gtk2KeyBindingsHandler::GetHandlerOwner(
198 GtkTextView
* text_view
) {
199 Handler
* handler
= G_TYPE_CHECK_INSTANCE_CAST(
200 text_view
, HandlerGetType(), Handler
);
202 return handler
->owner
;
205 void Gtk2KeyBindingsHandler::BackSpace(GtkTextView
* text_view
) {
206 GetHandlerOwner(text_view
)
207 ->EditCommandMatched(
208 TextEditCommandAuraLinux::DELETE_BACKWARD
, std::string(), false);
211 void Gtk2KeyBindingsHandler::CopyClipboard(GtkTextView
* text_view
) {
212 GetHandlerOwner(text_view
)->EditCommandMatched(
213 TextEditCommandAuraLinux::COPY
, std::string(), false);
216 void Gtk2KeyBindingsHandler::CutClipboard(GtkTextView
* text_view
) {
217 GetHandlerOwner(text_view
)->EditCommandMatched(
218 TextEditCommandAuraLinux::CUT
, std::string(), false);
221 void Gtk2KeyBindingsHandler::DeleteFromCursor(
222 GtkTextView
* text_view
, GtkDeleteType type
, gint count
) {
226 TextEditCommandAuraLinux::CommandId commands
[2] = {
227 TextEditCommandAuraLinux::INVALID_COMMAND
,
228 TextEditCommandAuraLinux::INVALID_COMMAND
,
231 case GTK_DELETE_CHARS
:
232 commands
[0] = (count
> 0 ?
233 TextEditCommandAuraLinux::DELETE_FORWARD
:
234 TextEditCommandAuraLinux::DELETE_BACKWARD
);
236 case GTK_DELETE_WORD_ENDS
:
237 commands
[0] = (count
> 0 ?
238 TextEditCommandAuraLinux::DELETE_WORD_FORWARD
:
239 TextEditCommandAuraLinux::DELETE_WORD_BACKWARD
);
241 case GTK_DELETE_WORDS
:
243 commands
[0] = TextEditCommandAuraLinux::MOVE_WORD_FORWARD
;
244 commands
[1] = TextEditCommandAuraLinux::DELETE_WORD_BACKWARD
;
246 commands
[0] = TextEditCommandAuraLinux::MOVE_WORD_BACKWARD
;
247 commands
[1] = TextEditCommandAuraLinux::DELETE_WORD_FORWARD
;
250 case GTK_DELETE_DISPLAY_LINES
:
251 commands
[0] = TextEditCommandAuraLinux::MOVE_TO_BEGINING_OF_LINE
;
252 commands
[1] = TextEditCommandAuraLinux::DELETE_TO_END_OF_LINE
;
254 case GTK_DELETE_DISPLAY_LINE_ENDS
:
255 commands
[0] = (count
> 0 ?
256 TextEditCommandAuraLinux::DELETE_TO_END_OF_LINE
:
257 TextEditCommandAuraLinux::DELETE_TO_BEGINING_OF_LINE
);
259 case GTK_DELETE_PARAGRAPH_ENDS
:
260 commands
[0] = (count
> 0 ?
261 TextEditCommandAuraLinux::DELETE_TO_END_OF_PARAGRAPH
:
262 TextEditCommandAuraLinux::DELETE_TO_BEGINING_OF_PARAGRAPH
);
264 case GTK_DELETE_PARAGRAPHS
:
266 TextEditCommandAuraLinux::MOVE_TO_BEGINING_OF_PARAGRAPH
;
268 TextEditCommandAuraLinux::DELETE_TO_END_OF_PARAGRAPH
;
271 // GTK_DELETE_WHITESPACE has no corresponding editor command.
275 Gtk2KeyBindingsHandler
* owner
= GetHandlerOwner(text_view
);
278 for (; count
> 0; --count
) {
279 for (size_t i
= 0; i
< arraysize(commands
); ++i
)
280 if (commands
[i
] != TextEditCommandAuraLinux::INVALID_COMMAND
)
281 owner
->EditCommandMatched(commands
[i
], std::string(), false);
285 void Gtk2KeyBindingsHandler::InsertAtCursor(GtkTextView
* text_view
,
288 GetHandlerOwner(text_view
)->EditCommandMatched(
289 TextEditCommandAuraLinux::INSERT_TEXT
, str
, false);
292 void Gtk2KeyBindingsHandler::MoveCursor(
293 GtkTextView
* text_view
, GtkMovementStep step
, gint count
,
294 gboolean extend_selection
) {
298 TextEditCommandAuraLinux::CommandId command
;
300 case GTK_MOVEMENT_LOGICAL_POSITIONS
:
301 command
= (count
> 0 ?
302 TextEditCommandAuraLinux::MOVE_FORWARD
:
303 TextEditCommandAuraLinux::MOVE_BACKWARD
);
305 case GTK_MOVEMENT_VISUAL_POSITIONS
:
306 command
= (count
> 0 ?
307 TextEditCommandAuraLinux::MOVE_RIGHT
:
308 TextEditCommandAuraLinux::MOVE_LEFT
);
310 case GTK_MOVEMENT_WORDS
:
311 command
= (count
> 0 ?
312 TextEditCommandAuraLinux::MOVE_WORD_RIGHT
:
313 TextEditCommandAuraLinux::MOVE_WORD_LEFT
);
315 case GTK_MOVEMENT_DISPLAY_LINES
:
316 command
= (count
> 0 ?
317 TextEditCommandAuraLinux::MOVE_DOWN
:
318 TextEditCommandAuraLinux::MOVE_UP
);
320 case GTK_MOVEMENT_DISPLAY_LINE_ENDS
:
321 command
= (count
> 0 ?
322 TextEditCommandAuraLinux::MOVE_TO_END_OF_LINE
:
323 TextEditCommandAuraLinux::MOVE_TO_BEGINING_OF_LINE
);
325 case GTK_MOVEMENT_PARAGRAPH_ENDS
:
326 command
= (count
> 0 ?
327 TextEditCommandAuraLinux::MOVE_TO_END_OF_PARAGRAPH
:
328 TextEditCommandAuraLinux::MOVE_TO_BEGINING_OF_PARAGRAPH
);
330 case GTK_MOVEMENT_PAGES
:
331 command
= (count
> 0 ? TextEditCommandAuraLinux::MOVE_PAGE_DOWN
:
332 TextEditCommandAuraLinux::MOVE_PAGE_UP
);
334 case GTK_MOVEMENT_BUFFER_ENDS
:
335 command
= (count
> 0 ? TextEditCommandAuraLinux::MOVE_TO_END_OF_DOCUMENT
:
336 TextEditCommandAuraLinux::MOVE_TO_BEGINING_OF_DOCUMENT
);
339 // GTK_MOVEMENT_PARAGRAPHS and GTK_MOVEMENT_HORIZONTAL_PAGES have
340 // no corresponding editor commands.
344 Gtk2KeyBindingsHandler
* owner
= GetHandlerOwner(text_view
);
347 for (; count
> 0; --count
)
348 owner
->EditCommandMatched(command
, std::string(), extend_selection
);
351 void Gtk2KeyBindingsHandler::MoveViewport(
352 GtkTextView
* text_view
, GtkScrollStep step
, gint count
) {
353 // Not supported by webkit.
356 void Gtk2KeyBindingsHandler::PasteClipboard(GtkTextView
* text_view
) {
357 GetHandlerOwner(text_view
)->EditCommandMatched(
358 TextEditCommandAuraLinux::PASTE
, std::string(), false);
361 void Gtk2KeyBindingsHandler::SelectAll(GtkTextView
* text_view
,
364 GetHandlerOwner(text_view
)->EditCommandMatched(
365 TextEditCommandAuraLinux::SELECT_ALL
, std::string(), false);
367 GetHandlerOwner(text_view
)->EditCommandMatched(
368 TextEditCommandAuraLinux::UNSELECT
, std::string(), false);
372 void Gtk2KeyBindingsHandler::SetAnchor(GtkTextView
* text_view
) {
373 GetHandlerOwner(text_view
)->EditCommandMatched(
374 TextEditCommandAuraLinux::SET_MARK
, std::string(), false);
377 void Gtk2KeyBindingsHandler::ToggleCursorVisible(GtkTextView
* text_view
) {
378 // Not supported by webkit.
381 void Gtk2KeyBindingsHandler::ToggleOverwrite(GtkTextView
* text_view
) {
382 // Not supported by webkit.
385 gboolean
Gtk2KeyBindingsHandler::ShowHelp(GtkWidget
* widget
,
386 GtkWidgetHelpType arg1
) {
387 // Just for disabling the default handler.
391 void Gtk2KeyBindingsHandler::MoveFocus(GtkWidget
* widget
,
392 GtkDirectionType arg1
) {
393 // Just for disabling the default handler.
396 } // namespace libgtk2ui