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
65 gtk_bindings_activate_event(
66 #if GDK_MAJOR_VERSION >= 3
67 G_OBJECT(handler_
.get()),
69 GTK_OBJECT(handler_
.get()),
73 bool matched
= !edit_commands_
.empty();
75 edit_commands
->swap(edit_commands_
);
79 GtkWidget
* Gtk2KeyBindingsHandler::CreateNewHandler() {
81 static_cast<Handler
*>(g_object_new(HandlerGetType(), NULL
));
83 handler
->owner
= this;
85 // We don't need to show the |handler| object on screen, so set its size to
87 gtk_widget_set_size_request(GTK_WIDGET(handler
), 0, 0);
89 // Prevents it from handling any events by itself.
90 gtk_widget_set_sensitive(GTK_WIDGET(handler
), FALSE
);
91 gtk_widget_set_events(GTK_WIDGET(handler
), 0);
92 gtk_widget_set_can_focus(GTK_WIDGET(handler
), TRUE
);
94 return GTK_WIDGET(handler
);
97 void Gtk2KeyBindingsHandler::EditCommandMatched(
98 TextEditCommandAuraLinux::CommandId id
,
99 const std::string
& value
,
100 bool extend_selection
) {
101 edit_commands_
.push_back(TextEditCommandAuraLinux(id
,
106 void Gtk2KeyBindingsHandler::BuildGdkEventKeyFromXEvent(
107 const base::NativeEvent
& xevent
,
108 GdkEventKey
* gdk_event
) {
109 GdkKeymap
*keymap
= gdk_keymap_get_for_display(gdk_display_get_default());
110 GdkModifierType consumed
, state
;
112 gdk_event
->type
= xevent
->xany
.type
== KeyPress
?
113 GDK_KEY_PRESS
: GDK_KEY_RELEASE
;
114 gdk_event
->time
= xevent
->xkey
.time
;
115 gdk_event
->state
= static_cast<GdkModifierType
>(xevent
->xkey
.state
);
116 gdk_event
->hardware_keycode
= xevent
->xkey
.keycode
;
119 gdk_event
->group
= XkbGroupForCoreState(xevent
->xkey
.state
);
121 // The overwhelming majority of people will be using X servers that support
122 // XKB. GDK has a fallback here that does some complicated stuff to detect
123 // whether a modifier key affects the keybinding, but that should be
125 static bool logged
= false;
130 gdk_event
->group
= 0;
133 gdk_event
->keyval
= GDK_KEY_VoidSymbol
;
134 gdk_keymap_translate_keyboard_state(
136 gdk_event
->hardware_keycode
,
137 static_cast<GdkModifierType
>(gdk_event
->state
),
140 NULL
, NULL
, &consumed
);
142 state
= static_cast<GdkModifierType
>(gdk_event
->state
& ~consumed
);
143 gdk_keymap_add_virtual_modifiers(keymap
, &state
);
144 gdk_event
->state
|= state
;
147 void Gtk2KeyBindingsHandler::HandlerInit(Handler
*self
) {
151 void Gtk2KeyBindingsHandler::HandlerClassInit(HandlerClass
*klass
) {
152 GtkTextViewClass
* text_view_class
= GTK_TEXT_VIEW_CLASS(klass
);
153 GtkWidgetClass
* widget_class
= GTK_WIDGET_CLASS(klass
);
155 // Overrides all virtual methods related to editor key bindings.
156 text_view_class
->backspace
= BackSpace
;
157 text_view_class
->copy_clipboard
= CopyClipboard
;
158 text_view_class
->cut_clipboard
= CutClipboard
;
159 text_view_class
->delete_from_cursor
= DeleteFromCursor
;
160 text_view_class
->insert_at_cursor
= InsertAtCursor
;
161 text_view_class
->move_cursor
= MoveCursor
;
162 text_view_class
->paste_clipboard
= PasteClipboard
;
163 text_view_class
->set_anchor
= SetAnchor
;
164 text_view_class
->toggle_overwrite
= ToggleOverwrite
;
165 widget_class
->show_help
= ShowHelp
;
167 // "move-focus", "move-viewport", "select-all" and "toggle-cursor-visible"
168 // have no corresponding virtual methods. Since glib 2.18 (gtk 2.14),
169 // g_signal_override_class_handler() is introduced to override a signal
171 g_signal_override_class_handler("move-focus",
172 G_TYPE_FROM_CLASS(klass
),
173 G_CALLBACK(MoveFocus
));
175 g_signal_override_class_handler("move-viewport",
176 G_TYPE_FROM_CLASS(klass
),
177 G_CALLBACK(MoveViewport
));
179 g_signal_override_class_handler("select-all",
180 G_TYPE_FROM_CLASS(klass
),
181 G_CALLBACK(SelectAll
));
183 g_signal_override_class_handler("toggle-cursor-visible",
184 G_TYPE_FROM_CLASS(klass
),
185 G_CALLBACK(ToggleCursorVisible
));
188 GType
Gtk2KeyBindingsHandler::HandlerGetType() {
189 static volatile gsize type_id_volatile
= 0;
190 if (g_once_init_enter(&type_id_volatile
)) {
191 GType type_id
= g_type_register_static_simple(
193 g_intern_static_string("Gtk2KeyBindingsHandler"),
194 sizeof(HandlerClass
),
195 reinterpret_cast<GClassInitFunc
>(HandlerClassInit
),
197 reinterpret_cast<GInstanceInitFunc
>(HandlerInit
),
198 static_cast<GTypeFlags
>(0));
199 g_once_init_leave(&type_id_volatile
, type_id
);
201 return type_id_volatile
;
204 Gtk2KeyBindingsHandler
* Gtk2KeyBindingsHandler::GetHandlerOwner(
205 GtkTextView
* text_view
) {
206 Handler
* handler
= G_TYPE_CHECK_INSTANCE_CAST(
207 text_view
, HandlerGetType(), Handler
);
209 return handler
->owner
;
212 void Gtk2KeyBindingsHandler::BackSpace(GtkTextView
* text_view
) {
213 GetHandlerOwner(text_view
)
214 ->EditCommandMatched(
215 TextEditCommandAuraLinux::DELETE_BACKWARD
, std::string(), false);
218 void Gtk2KeyBindingsHandler::CopyClipboard(GtkTextView
* text_view
) {
219 GetHandlerOwner(text_view
)->EditCommandMatched(
220 TextEditCommandAuraLinux::COPY
, std::string(), false);
223 void Gtk2KeyBindingsHandler::CutClipboard(GtkTextView
* text_view
) {
224 GetHandlerOwner(text_view
)->EditCommandMatched(
225 TextEditCommandAuraLinux::CUT
, std::string(), false);
228 void Gtk2KeyBindingsHandler::DeleteFromCursor(
229 GtkTextView
* text_view
, GtkDeleteType type
, gint count
) {
233 TextEditCommandAuraLinux::CommandId commands
[2] = {
234 TextEditCommandAuraLinux::INVALID_COMMAND
,
235 TextEditCommandAuraLinux::INVALID_COMMAND
,
238 case GTK_DELETE_CHARS
:
239 commands
[0] = (count
> 0 ?
240 TextEditCommandAuraLinux::DELETE_FORWARD
:
241 TextEditCommandAuraLinux::DELETE_BACKWARD
);
243 case GTK_DELETE_WORD_ENDS
:
244 commands
[0] = (count
> 0 ?
245 TextEditCommandAuraLinux::DELETE_WORD_FORWARD
:
246 TextEditCommandAuraLinux::DELETE_WORD_BACKWARD
);
248 case GTK_DELETE_WORDS
:
250 commands
[0] = TextEditCommandAuraLinux::MOVE_WORD_FORWARD
;
251 commands
[1] = TextEditCommandAuraLinux::DELETE_WORD_BACKWARD
;
253 commands
[0] = TextEditCommandAuraLinux::MOVE_WORD_BACKWARD
;
254 commands
[1] = TextEditCommandAuraLinux::DELETE_WORD_FORWARD
;
257 case GTK_DELETE_DISPLAY_LINES
:
258 commands
[0] = TextEditCommandAuraLinux::MOVE_TO_BEGINING_OF_LINE
;
259 commands
[1] = TextEditCommandAuraLinux::DELETE_TO_END_OF_LINE
;
261 case GTK_DELETE_DISPLAY_LINE_ENDS
:
262 commands
[0] = (count
> 0 ?
263 TextEditCommandAuraLinux::DELETE_TO_END_OF_LINE
:
264 TextEditCommandAuraLinux::DELETE_TO_BEGINING_OF_LINE
);
266 case GTK_DELETE_PARAGRAPH_ENDS
:
267 commands
[0] = (count
> 0 ?
268 TextEditCommandAuraLinux::DELETE_TO_END_OF_PARAGRAPH
:
269 TextEditCommandAuraLinux::DELETE_TO_BEGINING_OF_PARAGRAPH
);
271 case GTK_DELETE_PARAGRAPHS
:
273 TextEditCommandAuraLinux::MOVE_TO_BEGINING_OF_PARAGRAPH
;
275 TextEditCommandAuraLinux::DELETE_TO_END_OF_PARAGRAPH
;
278 // GTK_DELETE_WHITESPACE has no corresponding editor command.
282 Gtk2KeyBindingsHandler
* owner
= GetHandlerOwner(text_view
);
285 for (; count
> 0; --count
) {
286 for (size_t i
= 0; i
< arraysize(commands
); ++i
)
287 if (commands
[i
] != TextEditCommandAuraLinux::INVALID_COMMAND
)
288 owner
->EditCommandMatched(commands
[i
], std::string(), false);
292 void Gtk2KeyBindingsHandler::InsertAtCursor(GtkTextView
* text_view
,
295 GetHandlerOwner(text_view
)->EditCommandMatched(
296 TextEditCommandAuraLinux::INSERT_TEXT
, str
, false);
299 void Gtk2KeyBindingsHandler::MoveCursor(
300 GtkTextView
* text_view
, GtkMovementStep step
, gint count
,
301 gboolean extend_selection
) {
305 TextEditCommandAuraLinux::CommandId command
;
307 case GTK_MOVEMENT_LOGICAL_POSITIONS
:
308 command
= (count
> 0 ?
309 TextEditCommandAuraLinux::MOVE_FORWARD
:
310 TextEditCommandAuraLinux::MOVE_BACKWARD
);
312 case GTK_MOVEMENT_VISUAL_POSITIONS
:
313 command
= (count
> 0 ?
314 TextEditCommandAuraLinux::MOVE_RIGHT
:
315 TextEditCommandAuraLinux::MOVE_LEFT
);
317 case GTK_MOVEMENT_WORDS
:
318 command
= (count
> 0 ?
319 TextEditCommandAuraLinux::MOVE_WORD_RIGHT
:
320 TextEditCommandAuraLinux::MOVE_WORD_LEFT
);
322 case GTK_MOVEMENT_DISPLAY_LINES
:
323 command
= (count
> 0 ?
324 TextEditCommandAuraLinux::MOVE_DOWN
:
325 TextEditCommandAuraLinux::MOVE_UP
);
327 case GTK_MOVEMENT_DISPLAY_LINE_ENDS
:
328 command
= (count
> 0 ?
329 TextEditCommandAuraLinux::MOVE_TO_END_OF_LINE
:
330 TextEditCommandAuraLinux::MOVE_TO_BEGINING_OF_LINE
);
332 case GTK_MOVEMENT_PARAGRAPH_ENDS
:
333 command
= (count
> 0 ?
334 TextEditCommandAuraLinux::MOVE_TO_END_OF_PARAGRAPH
:
335 TextEditCommandAuraLinux::MOVE_TO_BEGINING_OF_PARAGRAPH
);
337 case GTK_MOVEMENT_PAGES
:
338 command
= (count
> 0 ? TextEditCommandAuraLinux::MOVE_PAGE_DOWN
:
339 TextEditCommandAuraLinux::MOVE_PAGE_UP
);
341 case GTK_MOVEMENT_BUFFER_ENDS
:
342 command
= (count
> 0 ? TextEditCommandAuraLinux::MOVE_TO_END_OF_DOCUMENT
:
343 TextEditCommandAuraLinux::MOVE_TO_BEGINING_OF_DOCUMENT
);
346 // GTK_MOVEMENT_PARAGRAPHS and GTK_MOVEMENT_HORIZONTAL_PAGES have
347 // no corresponding editor commands.
351 Gtk2KeyBindingsHandler
* owner
= GetHandlerOwner(text_view
);
354 for (; count
> 0; --count
)
355 owner
->EditCommandMatched(command
, std::string(), extend_selection
);
358 void Gtk2KeyBindingsHandler::MoveViewport(
359 GtkTextView
* text_view
, GtkScrollStep step
, gint count
) {
360 // Not supported by webkit.
363 void Gtk2KeyBindingsHandler::PasteClipboard(GtkTextView
* text_view
) {
364 GetHandlerOwner(text_view
)->EditCommandMatched(
365 TextEditCommandAuraLinux::PASTE
, std::string(), false);
368 void Gtk2KeyBindingsHandler::SelectAll(GtkTextView
* text_view
,
371 GetHandlerOwner(text_view
)->EditCommandMatched(
372 TextEditCommandAuraLinux::SELECT_ALL
, std::string(), false);
374 GetHandlerOwner(text_view
)->EditCommandMatched(
375 TextEditCommandAuraLinux::UNSELECT
, std::string(), false);
379 void Gtk2KeyBindingsHandler::SetAnchor(GtkTextView
* text_view
) {
380 GetHandlerOwner(text_view
)->EditCommandMatched(
381 TextEditCommandAuraLinux::SET_MARK
, std::string(), false);
384 void Gtk2KeyBindingsHandler::ToggleCursorVisible(GtkTextView
* text_view
) {
385 // Not supported by webkit.
388 void Gtk2KeyBindingsHandler::ToggleOverwrite(GtkTextView
* text_view
) {
389 // Not supported by webkit.
392 gboolean
Gtk2KeyBindingsHandler::ShowHelp(GtkWidget
* widget
,
393 GtkWidgetHelpType arg1
) {
394 // Just for disabling the default handler.
398 void Gtk2KeyBindingsHandler::MoveFocus(GtkWidget
* widget
,
399 GtkDirectionType arg1
) {
400 // Just for disabling the default handler.
403 } // namespace libgtk2ui