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
119 gdk_event
->group
= 0;
122 gdk_event
->keyval
= GDK_VoidSymbol
;
123 gdk_keymap_translate_keyboard_state(
125 gdk_event
->hardware_keycode
,
126 static_cast<GdkModifierType
>(gdk_event
->state
),
129 NULL
, NULL
, &consumed
);
131 state
= static_cast<GdkModifierType
>(gdk_event
->state
& ~consumed
);
132 gdk_keymap_add_virtual_modifiers(keymap
, &state
);
133 gdk_event
->state
|= state
;
136 void Gtk2KeyBindingsHandler::HandlerInit(Handler
*self
) {
140 void Gtk2KeyBindingsHandler::HandlerClassInit(HandlerClass
*klass
) {
141 GtkTextViewClass
* text_view_class
= GTK_TEXT_VIEW_CLASS(klass
);
142 GtkWidgetClass
* widget_class
= GTK_WIDGET_CLASS(klass
);
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
;
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
160 g_signal_override_class_handler("move-focus",
161 G_TYPE_FROM_CLASS(klass
),
162 G_CALLBACK(MoveFocus
));
164 g_signal_override_class_handler("move-viewport",
165 G_TYPE_FROM_CLASS(klass
),
166 G_CALLBACK(MoveViewport
));
168 g_signal_override_class_handler("select-all",
169 G_TYPE_FROM_CLASS(klass
),
170 G_CALLBACK(SelectAll
));
172 g_signal_override_class_handler("toggle-cursor-visible",
173 G_TYPE_FROM_CLASS(klass
),
174 G_CALLBACK(ToggleCursorVisible
));
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(
182 g_intern_static_string("Gtk2KeyBindingsHandler"),
183 sizeof(HandlerClass
),
184 reinterpret_cast<GClassInitFunc
>(HandlerClassInit
),
186 reinterpret_cast<GInstanceInitFunc
>(HandlerInit
),
187 static_cast<GTypeFlags
>(0));
188 g_once_init_leave(&type_id_volatile
, type_id
);
190 return type_id_volatile
;
193 Gtk2KeyBindingsHandler
* Gtk2KeyBindingsHandler::GetHandlerOwner(
194 GtkTextView
* text_view
) {
195 Handler
* handler
= G_TYPE_CHECK_INSTANCE_CAST(
196 text_view
, HandlerGetType(), Handler
);
198 return handler
->owner
;
201 void Gtk2KeyBindingsHandler::BackSpace(GtkTextView
* text_view
) {
202 GetHandlerOwner(text_view
)
203 ->EditCommandMatched(
204 TextEditCommandAuraLinux::DELETE_BACKWARD
, std::string(), false);
207 void Gtk2KeyBindingsHandler::CopyClipboard(GtkTextView
* text_view
) {
208 GetHandlerOwner(text_view
)->EditCommandMatched(
209 TextEditCommandAuraLinux::COPY
, std::string(), false);
212 void Gtk2KeyBindingsHandler::CutClipboard(GtkTextView
* text_view
) {
213 GetHandlerOwner(text_view
)->EditCommandMatched(
214 TextEditCommandAuraLinux::CUT
, std::string(), false);
217 void Gtk2KeyBindingsHandler::DeleteFromCursor(
218 GtkTextView
* text_view
, GtkDeleteType type
, gint count
) {
222 TextEditCommandAuraLinux::CommandId commands
[2] = {
223 TextEditCommandAuraLinux::INVALID_COMMAND
,
224 TextEditCommandAuraLinux::INVALID_COMMAND
,
227 case GTK_DELETE_CHARS
:
228 commands
[0] = (count
> 0 ?
229 TextEditCommandAuraLinux::DELETE_FORWARD
:
230 TextEditCommandAuraLinux::DELETE_BACKWARD
);
232 case GTK_DELETE_WORD_ENDS
:
233 commands
[0] = (count
> 0 ?
234 TextEditCommandAuraLinux::DELETE_WORD_FORWARD
:
235 TextEditCommandAuraLinux::DELETE_WORD_BACKWARD
);
237 case GTK_DELETE_WORDS
:
239 commands
[0] = TextEditCommandAuraLinux::MOVE_WORD_FORWARD
;
240 commands
[1] = TextEditCommandAuraLinux::DELETE_WORD_BACKWARD
;
242 commands
[0] = TextEditCommandAuraLinux::MOVE_WORD_BACKWARD
;
243 commands
[1] = TextEditCommandAuraLinux::DELETE_WORD_FORWARD
;
246 case GTK_DELETE_DISPLAY_LINES
:
247 commands
[0] = TextEditCommandAuraLinux::MOVE_TO_BEGINING_OF_LINE
;
248 commands
[1] = TextEditCommandAuraLinux::DELETE_TO_END_OF_LINE
;
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
);
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
);
260 case GTK_DELETE_PARAGRAPHS
:
262 TextEditCommandAuraLinux::MOVE_TO_BEGINING_OF_PARAGRAPH
;
264 TextEditCommandAuraLinux::DELETE_TO_END_OF_PARAGRAPH
;
267 // GTK_DELETE_WHITESPACE has no corresponding editor command.
271 Gtk2KeyBindingsHandler
* owner
= GetHandlerOwner(text_view
);
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);
281 void Gtk2KeyBindingsHandler::InsertAtCursor(GtkTextView
* text_view
,
284 GetHandlerOwner(text_view
)->EditCommandMatched(
285 TextEditCommandAuraLinux::INSERT_TEXT
, str
, false);
288 void Gtk2KeyBindingsHandler::MoveCursor(
289 GtkTextView
* text_view
, GtkMovementStep step
, gint count
,
290 gboolean extend_selection
) {
294 TextEditCommandAuraLinux::CommandId command
;
296 case GTK_MOVEMENT_LOGICAL_POSITIONS
:
297 command
= (count
> 0 ?
298 TextEditCommandAuraLinux::MOVE_FORWARD
:
299 TextEditCommandAuraLinux::MOVE_BACKWARD
);
301 case GTK_MOVEMENT_VISUAL_POSITIONS
:
302 command
= (count
> 0 ?
303 TextEditCommandAuraLinux::MOVE_RIGHT
:
304 TextEditCommandAuraLinux::MOVE_LEFT
);
306 case GTK_MOVEMENT_WORDS
:
307 command
= (count
> 0 ?
308 TextEditCommandAuraLinux::MOVE_WORD_RIGHT
:
309 TextEditCommandAuraLinux::MOVE_WORD_LEFT
);
311 case GTK_MOVEMENT_DISPLAY_LINES
:
312 command
= (count
> 0 ?
313 TextEditCommandAuraLinux::MOVE_DOWN
:
314 TextEditCommandAuraLinux::MOVE_UP
);
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
);
321 case GTK_MOVEMENT_PARAGRAPH_ENDS
:
322 command
= (count
> 0 ?
323 TextEditCommandAuraLinux::MOVE_TO_END_OF_PARAGRAPH
:
324 TextEditCommandAuraLinux::MOVE_TO_BEGINING_OF_PARAGRAPH
);
326 case GTK_MOVEMENT_PAGES
:
327 command
= (count
> 0 ? TextEditCommandAuraLinux::MOVE_PAGE_DOWN
:
328 TextEditCommandAuraLinux::MOVE_PAGE_UP
);
330 case GTK_MOVEMENT_BUFFER_ENDS
:
331 command
= (count
> 0 ? TextEditCommandAuraLinux::MOVE_TO_END_OF_DOCUMENT
:
332 TextEditCommandAuraLinux::MOVE_TO_BEGINING_OF_DOCUMENT
);
335 // GTK_MOVEMENT_PARAGRAPHS and GTK_MOVEMENT_HORIZONTAL_PAGES have
336 // no corresponding editor commands.
340 Gtk2KeyBindingsHandler
* owner
= GetHandlerOwner(text_view
);
343 for (; count
> 0; --count
)
344 owner
->EditCommandMatched(command
, std::string(), extend_selection
);
347 void Gtk2KeyBindingsHandler::MoveViewport(
348 GtkTextView
* text_view
, GtkScrollStep step
, gint count
) {
349 // Not supported by webkit.
352 void Gtk2KeyBindingsHandler::PasteClipboard(GtkTextView
* text_view
) {
353 GetHandlerOwner(text_view
)->EditCommandMatched(
354 TextEditCommandAuraLinux::PASTE
, std::string(), false);
357 void Gtk2KeyBindingsHandler::SelectAll(GtkTextView
* text_view
,
360 GetHandlerOwner(text_view
)->EditCommandMatched(
361 TextEditCommandAuraLinux::SELECT_ALL
, std::string(), false);
363 GetHandlerOwner(text_view
)->EditCommandMatched(
364 TextEditCommandAuraLinux::UNSELECT
, std::string(), false);
368 void Gtk2KeyBindingsHandler::SetAnchor(GtkTextView
* text_view
) {
369 GetHandlerOwner(text_view
)->EditCommandMatched(
370 TextEditCommandAuraLinux::SET_MARK
, std::string(), false);
373 void Gtk2KeyBindingsHandler::ToggleCursorVisible(GtkTextView
* text_view
) {
374 // Not supported by webkit.
377 void Gtk2KeyBindingsHandler::ToggleOverwrite(GtkTextView
* text_view
) {
378 // Not supported by webkit.
381 gboolean
Gtk2KeyBindingsHandler::ShowHelp(GtkWidget
* widget
,
382 GtkWidgetHelpType arg1
) {
383 // Just for disabling the default handler.
387 void Gtk2KeyBindingsHandler::MoveFocus(GtkWidget
* widget
,
388 GtkDirectionType arg1
) {
389 // Just for disabling the default handler.
392 } // namespace libgtk2ui