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::TextEditCommandX11
;
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
<TextEditCommandX11
>* 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 TextEditCommandX11::CommandId id
,
92 const std::string
& value
,
93 bool extend_selection
) {
94 edit_commands_
.push_back(TextEditCommandX11(id
, value
, extend_selection
));
97 void Gtk2KeyBindingsHandler::BuildGdkEventKeyFromXEvent(
98 const base::NativeEvent
& xevent
,
99 GdkEventKey
* gdk_event
) {
100 GdkKeymap
*keymap
= gdk_keymap_get_for_display(gdk_display_get_default());
101 GdkModifierType consumed
, state
;
103 gdk_event
->type
= xevent
->xany
.type
== KeyPress
?
104 GDK_KEY_PRESS
: GDK_KEY_RELEASE
;
105 gdk_event
->time
= xevent
->xkey
.time
;
106 gdk_event
->state
= static_cast<GdkModifierType
>(xevent
->xkey
.state
);
107 gdk_event
->hardware_keycode
= xevent
->xkey
.keycode
;
110 gdk_event
->group
= XkbGroupForCoreState(xevent
->xkey
.state
);
112 // The overwhelming majority of people will be using X servers that support
113 // XKB. GDK has a fallback here that does some complicated stuff to detect
114 // whether a modifier key affects the keybinding, but that should be
117 gdk_event
->group
= 0;
120 gdk_event
->keyval
= GDK_VoidSymbol
;
121 gdk_keymap_translate_keyboard_state(
123 gdk_event
->hardware_keycode
,
124 static_cast<GdkModifierType
>(gdk_event
->state
),
127 NULL
, NULL
, &consumed
);
129 state
= static_cast<GdkModifierType
>(gdk_event
->state
& ~consumed
);
130 gdk_keymap_add_virtual_modifiers(keymap
, &state
);
131 gdk_event
->state
|= state
;
134 void Gtk2KeyBindingsHandler::HandlerInit(Handler
*self
) {
138 void Gtk2KeyBindingsHandler::HandlerClassInit(HandlerClass
*klass
) {
139 GtkTextViewClass
* text_view_class
= GTK_TEXT_VIEW_CLASS(klass
);
140 GtkWidgetClass
* widget_class
= GTK_WIDGET_CLASS(klass
);
142 // Overrides all virtual methods related to editor key bindings.
143 text_view_class
->backspace
= BackSpace
;
144 text_view_class
->copy_clipboard
= CopyClipboard
;
145 text_view_class
->cut_clipboard
= CutClipboard
;
146 text_view_class
->delete_from_cursor
= DeleteFromCursor
;
147 text_view_class
->insert_at_cursor
= InsertAtCursor
;
148 text_view_class
->move_cursor
= MoveCursor
;
149 text_view_class
->paste_clipboard
= PasteClipboard
;
150 text_view_class
->set_anchor
= SetAnchor
;
151 text_view_class
->toggle_overwrite
= ToggleOverwrite
;
152 widget_class
->show_help
= ShowHelp
;
154 // "move-focus", "move-viewport", "select-all" and "toggle-cursor-visible"
155 // have no corresponding virtual methods. Since glib 2.18 (gtk 2.14),
156 // g_signal_override_class_handler() is introduced to override a signal
158 g_signal_override_class_handler("move-focus",
159 G_TYPE_FROM_CLASS(klass
),
160 G_CALLBACK(MoveFocus
));
162 g_signal_override_class_handler("move-viewport",
163 G_TYPE_FROM_CLASS(klass
),
164 G_CALLBACK(MoveViewport
));
166 g_signal_override_class_handler("select-all",
167 G_TYPE_FROM_CLASS(klass
),
168 G_CALLBACK(SelectAll
));
170 g_signal_override_class_handler("toggle-cursor-visible",
171 G_TYPE_FROM_CLASS(klass
),
172 G_CALLBACK(ToggleCursorVisible
));
175 GType
Gtk2KeyBindingsHandler::HandlerGetType() {
176 static volatile gsize type_id_volatile
= 0;
177 if (g_once_init_enter(&type_id_volatile
)) {
178 GType type_id
= g_type_register_static_simple(
180 g_intern_static_string("Gtk2KeyBindingsHandler"),
181 sizeof(HandlerClass
),
182 reinterpret_cast<GClassInitFunc
>(HandlerClassInit
),
184 reinterpret_cast<GInstanceInitFunc
>(HandlerInit
),
185 static_cast<GTypeFlags
>(0));
186 g_once_init_leave(&type_id_volatile
, type_id
);
188 return type_id_volatile
;
191 Gtk2KeyBindingsHandler
* Gtk2KeyBindingsHandler::GetHandlerOwner(
192 GtkTextView
* text_view
) {
193 Handler
* handler
= G_TYPE_CHECK_INSTANCE_CAST(
194 text_view
, HandlerGetType(), Handler
);
196 return handler
->owner
;
199 void Gtk2KeyBindingsHandler::BackSpace(GtkTextView
* text_view
) {
200 GetHandlerOwner(text_view
)
201 ->EditCommandMatched(
202 TextEditCommandX11::DELETE_BACKWARD
, std::string(), false);
205 void Gtk2KeyBindingsHandler::CopyClipboard(GtkTextView
* text_view
) {
206 GetHandlerOwner(text_view
)->EditCommandMatched(
207 TextEditCommandX11::COPY
, std::string(), false);
210 void Gtk2KeyBindingsHandler::CutClipboard(GtkTextView
* text_view
) {
211 GetHandlerOwner(text_view
)->EditCommandMatched(
212 TextEditCommandX11::CUT
, std::string(), false);
215 void Gtk2KeyBindingsHandler::DeleteFromCursor(
216 GtkTextView
* text_view
, GtkDeleteType type
, gint count
) {
220 TextEditCommandX11::CommandId commands
[2] = {
221 TextEditCommandX11::INVALID_COMMAND
,
222 TextEditCommandX11::INVALID_COMMAND
,
225 case GTK_DELETE_CHARS
:
226 commands
[0] = (count
> 0 ?
227 TextEditCommandX11::DELETE_FORWARD
:
228 TextEditCommandX11::DELETE_BACKWARD
);
230 case GTK_DELETE_WORD_ENDS
:
231 commands
[0] = (count
> 0 ?
232 TextEditCommandX11::DELETE_WORD_FORWARD
:
233 TextEditCommandX11::DELETE_WORD_BACKWARD
);
235 case GTK_DELETE_WORDS
:
237 commands
[0] = TextEditCommandX11::MOVE_WORD_FORWARD
;
238 commands
[1] = TextEditCommandX11::DELETE_WORD_BACKWARD
;
240 commands
[0] = TextEditCommandX11::MOVE_WORD_BACKWARD
;
241 commands
[1] = TextEditCommandX11::DELETE_WORD_FORWARD
;
244 case GTK_DELETE_DISPLAY_LINES
:
245 commands
[0] = TextEditCommandX11::MOVE_TO_BEGINING_OF_LINE
;
246 commands
[1] = TextEditCommandX11::DELETE_TO_END_OF_LINE
;
248 case GTK_DELETE_DISPLAY_LINE_ENDS
:
249 commands
[0] = (count
> 0 ?
250 TextEditCommandX11::DELETE_TO_END_OF_LINE
:
251 TextEditCommandX11::DELETE_TO_BEGINING_OF_LINE
);
253 case GTK_DELETE_PARAGRAPH_ENDS
:
254 commands
[0] = (count
> 0 ?
255 TextEditCommandX11::DELETE_TO_END_OF_PARAGRAPH
:
256 TextEditCommandX11::DELETE_TO_BEGINING_OF_PARAGRAPH
);
258 case GTK_DELETE_PARAGRAPHS
:
260 TextEditCommandX11::MOVE_TO_BEGINING_OF_PARAGRAPH
;
262 TextEditCommandX11::DELETE_TO_END_OF_PARAGRAPH
;
265 // GTK_DELETE_WHITESPACE has no corresponding editor command.
269 Gtk2KeyBindingsHandler
* owner
= GetHandlerOwner(text_view
);
272 for (; count
> 0; --count
) {
273 for (size_t i
= 0; i
< arraysize(commands
); ++i
)
274 if (commands
[i
] != TextEditCommandX11::INVALID_COMMAND
)
275 owner
->EditCommandMatched(commands
[i
], std::string(), false);
279 void Gtk2KeyBindingsHandler::InsertAtCursor(GtkTextView
* text_view
,
282 GetHandlerOwner(text_view
)->EditCommandMatched(
283 TextEditCommandX11::INSERT_TEXT
, str
, false);
286 void Gtk2KeyBindingsHandler::MoveCursor(
287 GtkTextView
* text_view
, GtkMovementStep step
, gint count
,
288 gboolean extend_selection
) {
292 TextEditCommandX11::CommandId command
;
294 case GTK_MOVEMENT_LOGICAL_POSITIONS
:
295 command
= (count
> 0 ?
296 TextEditCommandX11::MOVE_FORWARD
:
297 TextEditCommandX11::MOVE_BACKWARD
);
299 case GTK_MOVEMENT_VISUAL_POSITIONS
:
300 command
= (count
> 0 ?
301 TextEditCommandX11::MOVE_RIGHT
:
302 TextEditCommandX11::MOVE_LEFT
);
304 case GTK_MOVEMENT_WORDS
:
305 command
= (count
> 0 ?
306 TextEditCommandX11::MOVE_WORD_RIGHT
:
307 TextEditCommandX11::MOVE_WORD_LEFT
);
309 case GTK_MOVEMENT_DISPLAY_LINES
:
310 command
= (count
> 0 ?
311 TextEditCommandX11::MOVE_DOWN
: TextEditCommandX11::MOVE_UP
);
313 case GTK_MOVEMENT_DISPLAY_LINE_ENDS
:
314 command
= (count
> 0 ?
315 TextEditCommandX11::MOVE_TO_END_OF_LINE
:
316 TextEditCommandX11::MOVE_TO_BEGINING_OF_LINE
);
318 case GTK_MOVEMENT_PARAGRAPH_ENDS
:
319 command
= (count
> 0 ?
320 TextEditCommandX11::MOVE_TO_END_OF_PARAGRAPH
:
321 TextEditCommandX11::MOVE_TO_BEGINING_OF_PARAGRAPH
);
323 case GTK_MOVEMENT_PAGES
:
324 command
= (count
> 0 ? TextEditCommandX11::MOVE_PAGE_DOWN
:
325 TextEditCommandX11::MOVE_PAGE_UP
);
327 case GTK_MOVEMENT_BUFFER_ENDS
:
328 command
= (count
> 0 ? TextEditCommandX11::MOVE_TO_END_OF_DOCUMENT
:
329 TextEditCommandX11::MOVE_TO_BEGINING_OF_PARAGRAPH
);
332 // GTK_MOVEMENT_PARAGRAPHS and GTK_MOVEMENT_HORIZONTAL_PAGES have
333 // no corresponding editor commands.
337 Gtk2KeyBindingsHandler
* owner
= GetHandlerOwner(text_view
);
340 for (; count
> 0; --count
)
341 owner
->EditCommandMatched(command
, std::string(), extend_selection
);
344 void Gtk2KeyBindingsHandler::MoveViewport(
345 GtkTextView
* text_view
, GtkScrollStep step
, gint count
) {
346 // Not supported by webkit.
349 void Gtk2KeyBindingsHandler::PasteClipboard(GtkTextView
* text_view
) {
350 GetHandlerOwner(text_view
)->EditCommandMatched(
351 TextEditCommandX11::PASTE
, std::string(), false);
354 void Gtk2KeyBindingsHandler::SelectAll(GtkTextView
* text_view
,
357 GetHandlerOwner(text_view
)->EditCommandMatched(
358 TextEditCommandX11::SELECT_ALL
, std::string(), false);
360 GetHandlerOwner(text_view
)->EditCommandMatched(
361 TextEditCommandX11::UNSELECT
, std::string(), false);
365 void Gtk2KeyBindingsHandler::SetAnchor(GtkTextView
* text_view
) {
366 GetHandlerOwner(text_view
)->EditCommandMatched(
367 TextEditCommandX11::SET_MARK
, std::string(), false);
370 void Gtk2KeyBindingsHandler::ToggleCursorVisible(GtkTextView
* text_view
) {
371 // Not supported by webkit.
374 void Gtk2KeyBindingsHandler::ToggleOverwrite(GtkTextView
* text_view
) {
375 // Not supported by webkit.
378 gboolean
Gtk2KeyBindingsHandler::ShowHelp(GtkWidget
* widget
,
379 GtkWidgetHelpType arg1
) {
380 // Just for disabling the default handler.
384 void Gtk2KeyBindingsHandler::MoveFocus(GtkWidget
* widget
,
385 GtkDirectionType arg1
) {
386 // Just for disabling the default handler.
389 } // namespace libgtk2ui