1 // Copyright (c) 2013 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. 4 5 #include "ui/keyboard/keyboard_util.h" 6 7 #include <string> 8 9 #include "base/command_line.h" 10 #include "base/lazy_instance.h" 11 #include "base/logging.h" 12 #include "base/metrics/histogram.h" 13 #include "base/strings/string16.h" 14 #include "grit/keyboard_resources.h" 15 #include "grit/keyboard_resources_map.h" 16 #include "ui/aura/client/aura_constants.h" 17 #include "ui/aura/window_tree_host.h" 18 #include "ui/base/ime/input_method.h" 19 #include "ui/base/ime/text_input_client.h" 20 #include "ui/events/event_processor.h" 21 #include "ui/keyboard/keyboard_switches.h" 22 #include "url/gurl.h" 23 24 namespace { 25 26 const char kKeyDown[] ="keydown"; 27 const char kKeyUp[] = "keyup"; 28 29 void SendProcessKeyEvent(ui::EventType type, 30 aura::WindowTreeHost* host) { 31 ui::KeyEvent event(type, ui::VKEY_PROCESSKEY, ui::EF_NONE); 32 event.SetTranslated(true); 33 ui::EventDispatchDetails details = 34 host->event_processor()->OnEventFromSource(&event); 35 CHECK(!details.dispatcher_destroyed); 36 } 37 38 base::LazyInstance<base::Time> g_keyboard_load_time_start = 39 LAZY_INSTANCE_INITIALIZER; 40 41 bool g_accessibility_keyboard_enabled = false; 42 43 base::LazyInstance<GURL> g_override_content_url = LAZY_INSTANCE_INITIALIZER; 44 45 bool g_touch_keyboard_enabled = false; 46 47 keyboard::KeyboardOverscrolOverride g_keyboard_overscroll_override = 48 keyboard::KEYBOARD_OVERSCROLL_OVERRIDE_NONE; 49 50 keyboard::KeyboardShowOverride g_keyboard_show_override = 51 keyboard::KEYBOARD_SHOW_OVERRIDE_NONE; 52 53 } // namespace 54 55 namespace keyboard { 56 57 gfx::Rect DefaultKeyboardBoundsFromWindowBounds( 58 const gfx::Rect& window_bounds) { 59 // Initialize default keyboard height to 0. The keyboard window height should 60 // only be set by window.resizeTo in virtual keyboard web contents. Otherwise, 61 // the default height may conflict with the new height and causing some 62 // strange animation issues. 63 return KeyboardBoundsFromWindowBounds(window_bounds, 0); 64 } 65 66 gfx::Rect KeyboardBoundsFromWindowBounds(const gfx::Rect& window_bounds, 67 int keyboard_height) { 68 return gfx::Rect( 69 window_bounds.x(), 70 window_bounds.bottom() - keyboard_height, 71 window_bounds.width(), 72 keyboard_height); 73 } 74 75 void SetAccessibilityKeyboardEnabled(bool enabled) { 76 g_accessibility_keyboard_enabled = enabled; 77 } 78 79 bool GetAccessibilityKeyboardEnabled() { 80 return g_accessibility_keyboard_enabled; 81 } 82 83 void SetTouchKeyboardEnabled(bool enabled) { 84 g_touch_keyboard_enabled = enabled; 85 } 86 87 bool GetTouchKeyboardEnabled() { 88 return g_touch_keyboard_enabled; 89 } 90 91 std::string GetKeyboardLayout() { 92 // TODO(bshe): layout string is currently hard coded. We should use more 93 // standard keyboard layouts. 94 return GetAccessibilityKeyboardEnabled() ? "system-qwerty" : "qwerty"; 95 } 96 97 bool IsKeyboardEnabled() { 98 // Accessibility setting prioritized over policy setting. 99 if (g_accessibility_keyboard_enabled) 100 return true; 101 // Policy strictly disables showing a virtual keyboard. 102 if (g_keyboard_show_override == keyboard::KEYBOARD_SHOW_OVERRIDE_DISABLED) 103 return false; 104 // Check if any of the flags are enabled. 105 return CommandLine::ForCurrentProcess()->HasSwitch( 106 switches::kEnableVirtualKeyboard) || 107 g_touch_keyboard_enabled || 108 (g_keyboard_show_override == keyboard::KEYBOARD_SHOW_OVERRIDE_ENABLED); 109 } 110 111 bool IsKeyboardOverscrollEnabled() { 112 if (!IsKeyboardEnabled()) 113 return false; 114 115 // Users of the accessibility on-screen keyboard are likely to be using mouse 116 // input, which may interfere with overscrolling. 117 if (g_accessibility_keyboard_enabled) 118 return false; 119 120 // If overscroll enabled override is set, use it instead. Currently 121 // login / out-of-box disable keyboard overscroll. http://crbug.com/363635 122 if (g_keyboard_overscroll_override != KEYBOARD_OVERSCROLL_OVERRIDE_NONE) { 123 return g_keyboard_overscroll_override == 124 KEYBOARD_OVERSCROLL_OVERRIDE_ENABLED; 125 } 126 127 if (CommandLine::ForCurrentProcess()->HasSwitch( 128 switches::kDisableVirtualKeyboardOverscroll)) { 129 return false; 130 } 131 return true; 132 } 133 134 void SetKeyboardOverscrollOverride(KeyboardOverscrolOverride override) { 135 g_keyboard_overscroll_override = override; 136 } 137 138 void SetKeyboardShowOverride(KeyboardShowOverride override) { 139 g_keyboard_show_override = override; 140 } 141 142 bool IsInputViewEnabled() { 143 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kEnableInputView)) 144 return true; 145 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kDisableInputView)) 146 return false; 147 // Default value if no command line flags specified. 148 return true; 149 } 150 151 bool IsExperimentalInputViewEnabled() { 152 if (CommandLine::ForCurrentProcess()->HasSwitch( 153 switches::kEnableExperimentalInputViewFeatures)) { 154 return true; 155 } 156 return false; 157 } 158 159 bool InsertText(const base::string16& text, aura::Window* root_window) { 160 if (!root_window) 161 return false; 162 163 ui::InputMethod* input_method = root_window->GetProperty( 164 aura::client::kRootWindowInputMethodKey); 165 if (!input_method) 166 return false; 167 168 ui::TextInputClient* tic = input_method->GetTextInputClient(); 169 if (!tic || tic->GetTextInputType() == ui::TEXT_INPUT_TYPE_NONE) 170 return false; 171 172 tic->InsertText(text); 173 174 return true; 175 } 176 177 // TODO(varunjain): It would be cleaner to have something in the 178 // ui::TextInputClient interface, say MoveCaretInDirection(). The code in 179 // here would get the ui::InputMethod from the root_window, and the 180 // ui::TextInputClient from that (see above in InsertText()). 181 bool MoveCursor(int swipe_direction, 182 int modifier_flags, 183 aura::WindowTreeHost* host) { 184 if (!host) 185 return false; 186 ui::KeyboardCode codex = ui::VKEY_UNKNOWN; 187 ui::KeyboardCode codey = ui::VKEY_UNKNOWN; 188 if (swipe_direction & kCursorMoveRight) 189 codex = ui::VKEY_RIGHT; 190 else if (swipe_direction & kCursorMoveLeft) 191 codex = ui::VKEY_LEFT; 192 193 if (swipe_direction & kCursorMoveUp) 194 codey = ui::VKEY_UP; 195 else if (swipe_direction & kCursorMoveDown) 196 codey = ui::VKEY_DOWN; 197 198 // First deal with the x movement. 199 if (codex != ui::VKEY_UNKNOWN) { 200 ui::KeyEvent press_event(ui::ET_KEY_PRESSED, codex, modifier_flags); 201 ui::EventDispatchDetails details = 202 host->event_processor()->OnEventFromSource(&press_event); 203 CHECK(!details.dispatcher_destroyed); 204 ui::KeyEvent release_event(ui::ET_KEY_RELEASED, codex, modifier_flags); 205 details = host->event_processor()->OnEventFromSource(&release_event); 206 CHECK(!details.dispatcher_destroyed); 207 } 208 209 // Then deal with the y movement. 210 if (codey != ui::VKEY_UNKNOWN) { 211 ui::KeyEvent press_event(ui::ET_KEY_PRESSED, codey, modifier_flags); 212 ui::EventDispatchDetails details = 213 host->event_processor()->OnEventFromSource(&press_event); 214 CHECK(!details.dispatcher_destroyed); 215 ui::KeyEvent release_event(ui::ET_KEY_RELEASED, codey, modifier_flags); 216 details = host->event_processor()->OnEventFromSource(&release_event); 217 CHECK(!details.dispatcher_destroyed); 218 } 219 return true; 220 } 221 222 bool SendKeyEvent(const std::string type, 223 int key_value, 224 int key_code, 225 std::string key_name, 226 int modifiers, 227 aura::WindowTreeHost* host) { 228 ui::EventType event_type = ui::ET_UNKNOWN; 229 if (type == kKeyDown) 230 event_type = ui::ET_KEY_PRESSED; 231 else if (type == kKeyUp) 232 event_type = ui::ET_KEY_RELEASED; 233 if (event_type == ui::ET_UNKNOWN) 234 return false; 235 236 ui::KeyboardCode code = static_cast<ui::KeyboardCode>(key_code); 237 238 if (code == ui::VKEY_UNKNOWN) { 239 // Handling of special printable characters (e.g. accented characters) for 240 // which there is no key code. 241 if (event_type == ui::ET_KEY_RELEASED) { 242 ui::InputMethod* input_method = host->window()->GetProperty( 243 aura::client::kRootWindowInputMethodKey); 244 if (!input_method) 245 return false; 246 247 ui::TextInputClient* tic = input_method->GetTextInputClient(); 248 249 SendProcessKeyEvent(ui::ET_KEY_PRESSED, host); 250 tic->InsertChar(static_cast<uint16>(key_value), ui::EF_NONE); 251 SendProcessKeyEvent(ui::ET_KEY_RELEASED, host); 252 } 253 } else { 254 if (event_type == ui::ET_KEY_RELEASED) { 255 // The number of key press events seen since the last backspace. 256 static int keys_seen = 0; 257 if (code == ui::VKEY_BACK) { 258 // Log the rough lengths of characters typed between backspaces. This 259 // metric will be used to determine the error rate for the keyboard. 260 UMA_HISTOGRAM_CUSTOM_COUNTS( 261 "VirtualKeyboard.KeystrokesBetweenBackspaces", 262 keys_seen, 1, 1000, 50); 263 keys_seen = 0; 264 } else { 265 ++keys_seen; 266 } 267 } 268 269 ui::KeyEvent event(event_type, code, key_name, modifiers); 270 ui::EventDispatchDetails details = 271 host->event_processor()->OnEventFromSource(&event); 272 CHECK(!details.dispatcher_destroyed); 273 } 274 return true; 275 } 276 277 void MarkKeyboardLoadStarted() { 278 if (!g_keyboard_load_time_start.Get().ToInternalValue()) 279 g_keyboard_load_time_start.Get() = base::Time::Now(); 280 } 281 282 void MarkKeyboardLoadFinished() { 283 // Possible to get a load finished without a start if navigating directly to 284 // chrome://keyboard. 285 if (!g_keyboard_load_time_start.Get().ToInternalValue()) 286 return; 287 288 // It should not be possible to finish loading the keyboard without starting 289 // to load it first. 290 DCHECK(g_keyboard_load_time_start.Get().ToInternalValue()); 291 292 static bool logged = false; 293 if (!logged) { 294 // Log the delta only once. 295 UMA_HISTOGRAM_TIMES( 296 "VirtualKeyboard.FirstLoadTime", 297 base::Time::Now() - g_keyboard_load_time_start.Get()); 298 logged = true; 299 } 300 } 301 302 const GritResourceMap* GetKeyboardExtensionResources(size_t* size) { 303 // This looks a lot like the contents of a resource map; however it is 304 // necessary to have a custom path for the extension path, so the resource 305 // map cannot be used directly. 306 static const GritResourceMap kKeyboardResources[] = { 307 {"keyboard/layouts/function-key-row.html", IDR_KEYBOARD_FUNCTION_KEY_ROW}, 308 {"keyboard/images/back.svg", IDR_KEYBOARD_IMAGES_BACK}, 309 {"keyboard/images/backspace.png", IDR_KEYBOARD_IMAGES_BACKSPACE}, 310 {"keyboard/images/brightness-down.svg", 311 IDR_KEYBOARD_IMAGES_BRIGHTNESS_DOWN}, 312 {"keyboard/images/brightness-up.svg", IDR_KEYBOARD_IMAGES_BRIGHTNESS_UP}, 313 {"keyboard/images/change-window.svg", IDR_KEYBOARD_IMAGES_CHANGE_WINDOW}, 314 {"keyboard/images/down.svg", IDR_KEYBOARD_IMAGES_DOWN}, 315 {"keyboard/images/forward.svg", IDR_KEYBOARD_IMAGES_FORWARD}, 316 {"keyboard/images/fullscreen.svg", IDR_KEYBOARD_IMAGES_FULLSCREEN}, 317 {"keyboard/images/hide-keyboard.png", IDR_KEYBOARD_IMAGES_HIDE_KEYBOARD}, 318 {"keyboard/images/keyboard.svg", IDR_KEYBOARD_IMAGES_KEYBOARD}, 319 {"keyboard/images/left.svg", IDR_KEYBOARD_IMAGES_LEFT}, 320 {"keyboard/images/microphone.svg", IDR_KEYBOARD_IMAGES_MICROPHONE}, 321 {"keyboard/images/microphone-green.svg", 322 IDR_KEYBOARD_IMAGES_MICROPHONE_GREEN}, 323 {"keyboard/images/mute.svg", IDR_KEYBOARD_IMAGES_MUTE}, 324 {"keyboard/images/reload.svg", IDR_KEYBOARD_IMAGES_RELOAD}, 325 {"keyboard/images/return.png", IDR_KEYBOARD_IMAGES_RETURN}, 326 {"keyboard/images/right.svg", IDR_KEYBOARD_IMAGES_RIGHT}, 327 {"keyboard/images/search.png", IDR_KEYBOARD_IMAGES_SEARCH}, 328 {"keyboard/images/shift.png", IDR_KEYBOARD_IMAGES_SHIFT}, 329 {"keyboard/images/shutdown.svg", IDR_KEYBOARD_IMAGES_SHUTDOWN}, 330 {"keyboard/images/tab.png", IDR_KEYBOARD_IMAGES_TAB}, 331 {"keyboard/images/up.svg", IDR_KEYBOARD_IMAGES_UP}, 332 {"keyboard/images/volume-down.svg", IDR_KEYBOARD_IMAGES_VOLUME_DOWN}, 333 {"keyboard/images/volume-up.svg", IDR_KEYBOARD_IMAGES_VOLUME_UP}, 334 {"keyboard/index.html", IDR_KEYBOARD_INDEX}, 335 {"keyboard/keyboard.js", IDR_KEYBOARD_JS}, 336 {"keyboard/keyboard_mojo.js", IDR_KEYBOARD_MOJO_JS}, 337 {"keyboard/layouts/numeric.html", IDR_KEYBOARD_LAYOUTS_NUMERIC}, 338 {"keyboard/layouts/qwerty.html", IDR_KEYBOARD_LAYOUTS_QWERTY}, 339 {"keyboard/layouts/system-qwerty.html", 340 IDR_KEYBOARD_LAYOUTS_SYSTEM_QWERTY}, 341 {"keyboard/layouts/spacebar-row.html", IDR_KEYBOARD_SPACEBAR_ROW}, 342 {"keyboard/manifest.json", IDR_KEYBOARD_MANIFEST}, 343 {"keyboard/main.css", IDR_KEYBOARD_MAIN_CSS}, 344 {"keyboard/polymer_loader.js", IDR_KEYBOARD_POLYMER_LOADER}, 345 {"keyboard/roboto_bold.ttf", IDR_KEYBOARD_ROBOTO_BOLD_TTF}, 346 {"keyboard/sounds/keypress-delete.wav", 347 IDR_KEYBOARD_SOUNDS_KEYPRESS_DELETE}, 348 {"keyboard/sounds/keypress-return.wav", 349 IDR_KEYBOARD_SOUNDS_KEYPRESS_RETURN}, 350 {"keyboard/sounds/keypress-spacebar.wav", 351 IDR_KEYBOARD_SOUNDS_KEYPRESS_SPACEBAR}, 352 {"keyboard/sounds/keypress-standard.wav", 353 IDR_KEYBOARD_SOUNDS_KEYPRESS_STANDARD}, 354 }; 355 static const size_t kKeyboardResourcesSize = arraysize(kKeyboardResources); 356 *size = kKeyboardResourcesSize; 357 return kKeyboardResources; 358 } 359 360 void SetOverrideContentUrl(const GURL& url) { 361 g_override_content_url.Get() = url; 362 } 363 364 const GURL& GetOverrideContentUrl() { 365 return g_override_content_url.Get(); 366 } 367 368 void LogKeyboardControlEvent(KeyboardControlEvent event) { 369 UMA_HISTOGRAM_ENUMERATION( 370 "VirtualKeyboard.KeyboardControlEvent", 371 event, 372 keyboard::KEYBOARD_CONTROL_MAX); 373 } 374 375 } // namespace keyboard 376