widget/gtk/NativeKeyBindings.cpp

Thu, 15 Jan 2015 15:59:08 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 15 Jan 2015 15:59:08 +0100
branch
TOR_BUG_9701
changeset 10
ac0c01689b40
permissions
-rw-r--r--

Implement a real Private Browsing Mode condition by changing the API/ABI;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

michael@0 1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 3 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 5
michael@0 6 #include "mozilla/ArrayUtils.h"
michael@0 7 #include "mozilla/MathAlgorithms.h"
michael@0 8 #include "mozilla/TextEvents.h"
michael@0 9
michael@0 10 #include "NativeKeyBindings.h"
michael@0 11 #include "nsString.h"
michael@0 12 #include "nsMemory.h"
michael@0 13 #include "nsGtkKeyUtils.h"
michael@0 14
michael@0 15 #include <gtk/gtk.h>
michael@0 16 #include <gdk/gdkkeysyms.h>
michael@0 17 #include <gdk/gdk.h>
michael@0 18
michael@0 19 namespace mozilla {
michael@0 20 namespace widget {
michael@0 21
michael@0 22 static nsIWidget::DoCommandCallback gCurrentCallback;
michael@0 23 static void *gCurrentCallbackData;
michael@0 24 static bool gHandled;
michael@0 25
michael@0 26 // Common GtkEntry and GtkTextView signals
michael@0 27 static void
michael@0 28 copy_clipboard_cb(GtkWidget *w, gpointer user_data)
michael@0 29 {
michael@0 30 gCurrentCallback(CommandCopy, gCurrentCallbackData);
michael@0 31 g_signal_stop_emission_by_name(w, "copy_clipboard");
michael@0 32 gHandled = true;
michael@0 33 }
michael@0 34
michael@0 35 static void
michael@0 36 cut_clipboard_cb(GtkWidget *w, gpointer user_data)
michael@0 37 {
michael@0 38 gCurrentCallback(CommandCut, gCurrentCallbackData);
michael@0 39 g_signal_stop_emission_by_name(w, "cut_clipboard");
michael@0 40 gHandled = true;
michael@0 41 }
michael@0 42
michael@0 43 // GTK distinguishes between display lines (wrapped, as they appear on the
michael@0 44 // screen) and paragraphs, which are runs of text terminated by a newline.
michael@0 45 // We don't have this distinction, so we always use editor's notion of
michael@0 46 // lines, which are newline-terminated.
michael@0 47
michael@0 48 static const Command sDeleteCommands[][2] = {
michael@0 49 // backward, forward
michael@0 50 { CommandDeleteCharBackward, CommandDeleteCharForward }, // CHARS
michael@0 51 { CommandDeleteWordBackward, CommandDeleteWordForward }, // WORD_ENDS
michael@0 52 { CommandDeleteWordBackward, CommandDeleteWordForward }, // WORDS
michael@0 53 { CommandDeleteToBeginningOfLine, CommandDeleteToEndOfLine }, // LINES
michael@0 54 { CommandDeleteToBeginningOfLine, CommandDeleteToEndOfLine }, // LINE_ENDS
michael@0 55 { CommandDeleteToBeginningOfLine, CommandDeleteToEndOfLine }, // PARAGRAPH_ENDS
michael@0 56 { CommandDeleteToBeginningOfLine, CommandDeleteToEndOfLine }, // PARAGRAPHS
michael@0 57 // This deletes from the end of the previous word to the beginning of the
michael@0 58 // next word, but only if the caret is not in a word.
michael@0 59 // XXX need to implement in editor
michael@0 60 { CommandDoNothing, CommandDoNothing } // WHITESPACE
michael@0 61 };
michael@0 62
michael@0 63 static void
michael@0 64 delete_from_cursor_cb(GtkWidget *w, GtkDeleteType del_type,
michael@0 65 gint count, gpointer user_data)
michael@0 66 {
michael@0 67 g_signal_stop_emission_by_name(w, "delete_from_cursor");
michael@0 68 gHandled = true;
michael@0 69
michael@0 70 bool forward = count > 0;
michael@0 71 if (uint32_t(del_type) >= ArrayLength(sDeleteCommands)) {
michael@0 72 // unsupported deletion type
michael@0 73 return;
michael@0 74 }
michael@0 75
michael@0 76 if (del_type == GTK_DELETE_WORDS) {
michael@0 77 // This works like word_ends, except we first move the caret to the
michael@0 78 // beginning/end of the current word.
michael@0 79 if (forward) {
michael@0 80 gCurrentCallback(CommandWordNext, gCurrentCallbackData);
michael@0 81 gCurrentCallback(CommandWordPrevious, gCurrentCallbackData);
michael@0 82 } else {
michael@0 83 gCurrentCallback(CommandWordPrevious, gCurrentCallbackData);
michael@0 84 gCurrentCallback(CommandWordNext, gCurrentCallbackData);
michael@0 85 }
michael@0 86 } else if (del_type == GTK_DELETE_DISPLAY_LINES ||
michael@0 87 del_type == GTK_DELETE_PARAGRAPHS) {
michael@0 88
michael@0 89 // This works like display_line_ends, except we first move the caret to the
michael@0 90 // beginning/end of the current line.
michael@0 91 if (forward) {
michael@0 92 gCurrentCallback(CommandBeginLine, gCurrentCallbackData);
michael@0 93 } else {
michael@0 94 gCurrentCallback(CommandEndLine, gCurrentCallbackData);
michael@0 95 }
michael@0 96 }
michael@0 97
michael@0 98 Command command = sDeleteCommands[del_type][forward];
michael@0 99 if (!command) {
michael@0 100 return; // unsupported command
michael@0 101 }
michael@0 102
michael@0 103 unsigned int absCount = Abs(count);
michael@0 104 for (unsigned int i = 0; i < absCount; ++i) {
michael@0 105 gCurrentCallback(command, gCurrentCallbackData);
michael@0 106 }
michael@0 107 }
michael@0 108
michael@0 109 static const Command sMoveCommands[][2][2] = {
michael@0 110 // non-extend { backward, forward }, extend { backward, forward }
michael@0 111 // GTK differentiates between logical position, which is prev/next,
michael@0 112 // and visual position, which is always left/right.
michael@0 113 // We should fix this to work the same way for RTL text input.
michael@0 114 { // LOGICAL_POSITIONS
michael@0 115 { CommandCharPrevious, CommandCharNext },
michael@0 116 { CommandSelectCharPrevious, CommandSelectCharNext }
michael@0 117 },
michael@0 118 { // VISUAL_POSITIONS
michael@0 119 { CommandCharPrevious, CommandCharNext },
michael@0 120 { CommandSelectCharPrevious, CommandSelectCharNext }
michael@0 121 },
michael@0 122 { // WORDS
michael@0 123 { CommandWordPrevious, CommandWordNext },
michael@0 124 { CommandSelectWordPrevious, CommandSelectWordNext }
michael@0 125 },
michael@0 126 { // DISPLAY_LINES
michael@0 127 { CommandLinePrevious, CommandLineNext },
michael@0 128 { CommandSelectLinePrevious, CommandSelectLineNext }
michael@0 129 },
michael@0 130 { // DISPLAY_LINE_ENDS
michael@0 131 { CommandBeginLine, CommandEndLine },
michael@0 132 { CommandSelectBeginLine, CommandSelectEndLine }
michael@0 133 },
michael@0 134 { // PARAGRAPHS
michael@0 135 { CommandLinePrevious, CommandLineNext },
michael@0 136 { CommandSelectLinePrevious, CommandSelectLineNext }
michael@0 137 },
michael@0 138 { // PARAGRAPH_ENDS
michael@0 139 { CommandBeginLine, CommandEndLine },
michael@0 140 { CommandSelectBeginLine, CommandSelectEndLine }
michael@0 141 },
michael@0 142 { // PAGES
michael@0 143 { CommandMovePageUp, CommandMovePageDown },
michael@0 144 { CommandSelectPageUp, CommandSelectPageDown }
michael@0 145 },
michael@0 146 { // BUFFER_ENDS
michael@0 147 { CommandMoveTop, CommandMoveBottom },
michael@0 148 { CommandSelectTop, CommandSelectBottom }
michael@0 149 },
michael@0 150 { // HORIZONTAL_PAGES (unsupported)
michael@0 151 { CommandDoNothing, CommandDoNothing },
michael@0 152 { CommandDoNothing, CommandDoNothing }
michael@0 153 }
michael@0 154 };
michael@0 155
michael@0 156 static void
michael@0 157 move_cursor_cb(GtkWidget *w, GtkMovementStep step, gint count,
michael@0 158 gboolean extend_selection, gpointer user_data)
michael@0 159 {
michael@0 160 g_signal_stop_emission_by_name(w, "move_cursor");
michael@0 161 gHandled = true;
michael@0 162 bool forward = count > 0;
michael@0 163 if (uint32_t(step) >= ArrayLength(sMoveCommands)) {
michael@0 164 // unsupported movement type
michael@0 165 return;
michael@0 166 }
michael@0 167
michael@0 168 Command command = sMoveCommands[step][extend_selection][forward];
michael@0 169 if (!command) {
michael@0 170 return; // unsupported command
michael@0 171 }
michael@0 172
michael@0 173 unsigned int absCount = Abs(count);
michael@0 174 for (unsigned int i = 0; i < absCount; ++i) {
michael@0 175 gCurrentCallback(command, gCurrentCallbackData);
michael@0 176 }
michael@0 177 }
michael@0 178
michael@0 179 static void
michael@0 180 paste_clipboard_cb(GtkWidget *w, gpointer user_data)
michael@0 181 {
michael@0 182 gCurrentCallback(CommandPaste, gCurrentCallbackData);
michael@0 183 g_signal_stop_emission_by_name(w, "paste_clipboard");
michael@0 184 gHandled = true;
michael@0 185 }
michael@0 186
michael@0 187 // GtkTextView-only signals
michael@0 188 static void
michael@0 189 select_all_cb(GtkWidget *w, gboolean select, gpointer user_data)
michael@0 190 {
michael@0 191 gCurrentCallback(CommandSelectAll, gCurrentCallbackData);
michael@0 192 g_signal_stop_emission_by_name(w, "select_all");
michael@0 193 gHandled = true;
michael@0 194 }
michael@0 195
michael@0 196 NativeKeyBindings* NativeKeyBindings::sInstanceForSingleLineEditor = nullptr;
michael@0 197 NativeKeyBindings* NativeKeyBindings::sInstanceForMultiLineEditor = nullptr;
michael@0 198
michael@0 199 // static
michael@0 200 NativeKeyBindings*
michael@0 201 NativeKeyBindings::GetInstance(NativeKeyBindingsType aType)
michael@0 202 {
michael@0 203 switch (aType) {
michael@0 204 case nsIWidget::NativeKeyBindingsForSingleLineEditor:
michael@0 205 if (!sInstanceForSingleLineEditor) {
michael@0 206 sInstanceForSingleLineEditor = new NativeKeyBindings();
michael@0 207 sInstanceForSingleLineEditor->Init(aType);
michael@0 208 }
michael@0 209 return sInstanceForSingleLineEditor;
michael@0 210
michael@0 211 default:
michael@0 212 // fallback to multiline editor case in release build
michael@0 213 MOZ_ASSERT(false, "aType is invalid or not yet implemented");
michael@0 214 case nsIWidget::NativeKeyBindingsForMultiLineEditor:
michael@0 215 case nsIWidget::NativeKeyBindingsForRichTextEditor:
michael@0 216 if (!sInstanceForMultiLineEditor) {
michael@0 217 sInstanceForMultiLineEditor = new NativeKeyBindings();
michael@0 218 sInstanceForMultiLineEditor->Init(aType);
michael@0 219 }
michael@0 220 return sInstanceForMultiLineEditor;
michael@0 221 }
michael@0 222 }
michael@0 223
michael@0 224 // static
michael@0 225 void
michael@0 226 NativeKeyBindings::Shutdown()
michael@0 227 {
michael@0 228 delete sInstanceForSingleLineEditor;
michael@0 229 sInstanceForSingleLineEditor = nullptr;
michael@0 230 delete sInstanceForMultiLineEditor;
michael@0 231 sInstanceForMultiLineEditor = nullptr;
michael@0 232 }
michael@0 233
michael@0 234 void
michael@0 235 NativeKeyBindings::Init(NativeKeyBindingsType aType)
michael@0 236 {
michael@0 237 switch (aType) {
michael@0 238 case nsIWidget::NativeKeyBindingsForSingleLineEditor:
michael@0 239 mNativeTarget = gtk_entry_new();
michael@0 240 break;
michael@0 241 default:
michael@0 242 mNativeTarget = gtk_text_view_new();
michael@0 243 if (gtk_major_version > 2 ||
michael@0 244 (gtk_major_version == 2 && (gtk_minor_version > 2 ||
michael@0 245 (gtk_minor_version == 2 &&
michael@0 246 gtk_micro_version >= 2)))) {
michael@0 247 // select_all only exists in gtk >= 2.2.2. Prior to that,
michael@0 248 // ctrl+a is bound to (move to beginning, select to end).
michael@0 249 g_signal_connect(mNativeTarget, "select_all",
michael@0 250 G_CALLBACK(select_all_cb), this);
michael@0 251 }
michael@0 252 break;
michael@0 253 }
michael@0 254
michael@0 255 g_object_ref_sink(mNativeTarget);
michael@0 256
michael@0 257 g_signal_connect(mNativeTarget, "copy_clipboard",
michael@0 258 G_CALLBACK(copy_clipboard_cb), this);
michael@0 259 g_signal_connect(mNativeTarget, "cut_clipboard",
michael@0 260 G_CALLBACK(cut_clipboard_cb), this);
michael@0 261 g_signal_connect(mNativeTarget, "delete_from_cursor",
michael@0 262 G_CALLBACK(delete_from_cursor_cb), this);
michael@0 263 g_signal_connect(mNativeTarget, "move_cursor",
michael@0 264 G_CALLBACK(move_cursor_cb), this);
michael@0 265 g_signal_connect(mNativeTarget, "paste_clipboard",
michael@0 266 G_CALLBACK(paste_clipboard_cb), this);
michael@0 267 }
michael@0 268
michael@0 269 NativeKeyBindings::~NativeKeyBindings()
michael@0 270 {
michael@0 271 gtk_widget_destroy(mNativeTarget);
michael@0 272 g_object_unref(mNativeTarget);
michael@0 273 }
michael@0 274
michael@0 275 bool
michael@0 276 NativeKeyBindings::Execute(const WidgetKeyboardEvent& aEvent,
michael@0 277 DoCommandCallback aCallback,
michael@0 278 void* aCallbackData)
michael@0 279 {
michael@0 280 // If the native key event is set, it must be synthesized for tests.
michael@0 281 // We just ignore such events because this behavior depends on system
michael@0 282 // settings.
michael@0 283 if (!aEvent.mNativeKeyEvent) {
michael@0 284 // It must be synthesized event or dispatched DOM event from chrome.
michael@0 285 return false;
michael@0 286 }
michael@0 287
michael@0 288 guint keyval;
michael@0 289
michael@0 290 if (aEvent.charCode) {
michael@0 291 keyval = gdk_unicode_to_keyval(aEvent.charCode);
michael@0 292 } else {
michael@0 293 keyval =
michael@0 294 static_cast<GdkEventKey*>(aEvent.mNativeKeyEvent)->keyval;
michael@0 295 }
michael@0 296
michael@0 297 if (ExecuteInternal(aEvent, aCallback, aCallbackData, keyval)) {
michael@0 298 return true;
michael@0 299 }
michael@0 300
michael@0 301 for (uint32_t i = 0; i < aEvent.alternativeCharCodes.Length(); ++i) {
michael@0 302 uint32_t ch = aEvent.IsShift() ?
michael@0 303 aEvent.alternativeCharCodes[i].mShiftedCharCode :
michael@0 304 aEvent.alternativeCharCodes[i].mUnshiftedCharCode;
michael@0 305 if (ch && ch != aEvent.charCode) {
michael@0 306 keyval = gdk_unicode_to_keyval(ch);
michael@0 307 if (ExecuteInternal(aEvent, aCallback, aCallbackData, keyval)) {
michael@0 308 return true;
michael@0 309 }
michael@0 310 }
michael@0 311 }
michael@0 312
michael@0 313 /*
michael@0 314 gtk_bindings_activate_event is preferable, but it has unresolved bug:
michael@0 315 http://bugzilla.gnome.org/show_bug.cgi?id=162726
michael@0 316 The bug was already marked as FIXED. However, somebody reports that the
michael@0 317 bug still exists.
michael@0 318 Also gtk_bindings_activate may work with some non-shortcuts operations
michael@0 319 (todo: check it). See bug 411005 and bug 406407.
michael@0 320
michael@0 321 Code, which should be used after fixing GNOME bug 162726:
michael@0 322
michael@0 323 gtk_bindings_activate_event(GTK_OBJECT(mNativeTarget),
michael@0 324 static_cast<GdkEventKey*>(aEvent.mNativeKeyEvent));
michael@0 325 */
michael@0 326
michael@0 327 return false;
michael@0 328 }
michael@0 329
michael@0 330 bool
michael@0 331 NativeKeyBindings::ExecuteInternal(const WidgetKeyboardEvent& aEvent,
michael@0 332 DoCommandCallback aCallback,
michael@0 333 void* aCallbackData,
michael@0 334 guint aKeyval)
michael@0 335 {
michael@0 336 guint modifiers =
michael@0 337 static_cast<GdkEventKey*>(aEvent.mNativeKeyEvent)->state;
michael@0 338
michael@0 339 gCurrentCallback = aCallback;
michael@0 340 gCurrentCallbackData = aCallbackData;
michael@0 341
michael@0 342 gHandled = false;
michael@0 343 #if (MOZ_WIDGET_GTK == 2)
michael@0 344 gtk_bindings_activate(GTK_OBJECT(mNativeTarget),
michael@0 345 aKeyval, GdkModifierType(modifiers));
michael@0 346 #else
michael@0 347 gtk_bindings_activate(G_OBJECT(mNativeTarget),
michael@0 348 aKeyval, GdkModifierType(modifiers));
michael@0 349 #endif
michael@0 350
michael@0 351 gCurrentCallback = nullptr;
michael@0 352 gCurrentCallbackData = nullptr;
michael@0 353
michael@0 354 return gHandled;
michael@0 355 }
michael@0 356
michael@0 357 } // namespace widget
michael@0 358 } // namespace mozilla

mercurial