blob: ecc8e29c52f05036d197dbebf22f2ea59b651f55 [file] [log] [blame]
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/keyboard/keyboard_util.h"
#include <string>
#include "base/basictypes.h"
#include "base/command_line.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/metrics/histogram.h"
#include "base/strings/string16.h"
#include "grit/keyboard_resources.h"
#include "grit/keyboard_resources_map.h"
#include "media/audio/audio_manager.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/window_tree_host.h"
#include "ui/base/ime/input_method.h"
#include "ui/base/ime/text_input_client.h"
#include "ui/events/event_processor.h"
#include "ui/events/event_utils.h"
#include "ui/events/keycodes/dom/dom_code.h"
#include "ui/events/keycodes/dom/dom_key.h"
#include "ui/events/keycodes/dom/keycode_converter.h"
#include "ui/events/keycodes/keyboard_code_conversion.h"
#include "ui/keyboard/keyboard_controller.h"
#include "ui/keyboard/keyboard_controller_proxy.h"
#include "ui/keyboard/keyboard_switches.h"
#include "url/gurl.h"
namespace {
const char kKeyDown[] ="keydown";
const char kKeyUp[] = "keyup";
void SendProcessKeyEvent(ui::EventType type,
aura::WindowTreeHost* host) {
ui::KeyEvent event(type, ui::VKEY_PROCESSKEY, ui::DomCode::NONE,
ui::EF_IS_SYNTHESIZED, ui::DomKey::PROCESS,
ui::EventTimeForNow());
ui::EventDispatchDetails details =
host->event_processor()->OnEventFromSource(&event);
CHECK(!details.dispatcher_destroyed);
}
base::LazyInstance<base::Time> g_keyboard_load_time_start =
LAZY_INSTANCE_INITIALIZER;
bool g_accessibility_keyboard_enabled = false;
bool g_hotrod_keyboard_enabled = false;
base::LazyInstance<GURL> g_override_content_url = LAZY_INSTANCE_INITIALIZER;
bool g_touch_keyboard_enabled = false;
keyboard::KeyboardState g_requested_keyboard_state =
keyboard::KEYBOARD_STATE_AUTO;
keyboard::KeyboardOverscrolOverride g_keyboard_overscroll_override =
keyboard::KEYBOARD_OVERSCROLL_OVERRIDE_NONE;
keyboard::KeyboardShowOverride g_keyboard_show_override =
keyboard::KEYBOARD_SHOW_OVERRIDE_NONE;
} // namespace
namespace keyboard {
gfx::Rect FullWidthKeyboardBoundsFromRootBounds(const gfx::Rect& root_bounds,
int keyboard_height) {
return gfx::Rect(
root_bounds.x(),
root_bounds.bottom() - keyboard_height,
root_bounds.width(),
keyboard_height);
}
void SetAccessibilityKeyboardEnabled(bool enabled) {
g_accessibility_keyboard_enabled = enabled;
}
bool GetAccessibilityKeyboardEnabled() {
return g_accessibility_keyboard_enabled;
}
void SetHotrodKeyboardEnabled(bool enabled) {
g_hotrod_keyboard_enabled = enabled;
}
bool GetHotrodKeyboardEnabled() {
return g_hotrod_keyboard_enabled;
}
void SetTouchKeyboardEnabled(bool enabled) {
g_touch_keyboard_enabled = enabled;
}
bool GetTouchKeyboardEnabled() {
return g_touch_keyboard_enabled;
}
void SetRequestedKeyboardState(KeyboardState state) {
g_requested_keyboard_state = state;
}
KeyboardState GetKeyboardRequestedState() {
return g_requested_keyboard_state;
}
std::string GetKeyboardLayout() {
// TODO(bshe): layout string is currently hard coded. We should use more
// standard keyboard layouts.
return GetAccessibilityKeyboardEnabled() ? "system-qwerty" : "qwerty";
}
bool IsKeyboardEnabled() {
// Accessibility setting prioritized over policy setting.
if (g_accessibility_keyboard_enabled)
return true;
// Policy strictly disables showing a virtual keyboard.
if (g_keyboard_show_override == keyboard::KEYBOARD_SHOW_OVERRIDE_DISABLED)
return false;
// Policy strictly enables the keyboard.
if (g_keyboard_show_override == keyboard::KEYBOARD_SHOW_OVERRIDE_ENABLED)
return true;
// Run-time flag to enable keyboard has been included.
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kEnableVirtualKeyboard))
return true;
// Requested state from the application layer.
if (g_requested_keyboard_state == keyboard::KEYBOARD_STATE_DISABLED)
return false;
// Check if any of the other flags are enabled.
return g_touch_keyboard_enabled ||
g_requested_keyboard_state == keyboard::KEYBOARD_STATE_ENABLED;
}
bool IsKeyboardOverscrollEnabled() {
if (!IsKeyboardEnabled())
return false;
// Users of the accessibility on-screen keyboard are likely to be using mouse
// input, which may interfere with overscrolling.
if (g_accessibility_keyboard_enabled)
return false;
// If overscroll enabled override is set, use it instead. Currently
// login / out-of-box disable keyboard overscroll. http://crbug.com/363635
if (g_keyboard_overscroll_override != KEYBOARD_OVERSCROLL_OVERRIDE_NONE) {
return g_keyboard_overscroll_override ==
KEYBOARD_OVERSCROLL_OVERRIDE_ENABLED;
}
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kDisableVirtualKeyboardOverscroll)) {
return false;
}
return true;
}
void SetKeyboardOverscrollOverride(KeyboardOverscrolOverride override) {
g_keyboard_overscroll_override = override;
}
void SetKeyboardShowOverride(KeyboardShowOverride override) {
g_keyboard_show_override = override;
}
bool IsInputViewEnabled() {
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kEnableInputView))
return true;
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kDisableInputView))
return false;
// Default value if no command line flags specified.
return true;
}
bool IsExperimentalInputViewEnabled() {
return base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kEnableExperimentalInputViewFeatures);
}
bool IsFloatingVirtualKeyboardEnabled() {
std::string floating_virtual_keyboard_switch =
base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
switches::kFloatingVirtualKeyboard);
return floating_virtual_keyboard_switch ==
switches::kFloatingVirtualKeyboardEnabled;
}
bool IsGestureTypingEnabled() {
return base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
switches::kGestureTyping) != switches::kGestureTypingDisabled;
}
bool IsGestureEditingEnabled() {
std::string keyboard_switch =
base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
switches::kGestureEditing);
return keyboard_switch != switches::kGestureEditingDisabled;
}
bool IsSmartDeployEnabled() {
std::string keyboard_switch =
base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
switches::kSmartVirtualKeyboard);
return keyboard_switch != switches::kSmartVirtualKeyboardDisabled;
}
bool IsVoiceInputEnabled() {
return media::AudioManager::Get()->HasAudioInputDevices() &&
!base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kDisableVoiceInput);
}
bool InsertText(const base::string16& text) {
keyboard::KeyboardController* controller = KeyboardController::GetInstance();
if (!controller)
return false;
ui::InputMethod* input_method = controller->proxy()->GetInputMethod();
if (!input_method)
return false;
ui::TextInputClient* tic = input_method->GetTextInputClient();
if (!tic || tic->GetTextInputType() == ui::TEXT_INPUT_TYPE_NONE)
return false;
tic->InsertText(text);
return true;
}
// TODO(varunjain): It would be cleaner to have something in the
// ui::TextInputClient interface, say MoveCaretInDirection(). The code in
// here would get the ui::InputMethod from the root_window, and the
// ui::TextInputClient from that (see above in InsertText()).
bool MoveCursor(int swipe_direction,
int modifier_flags,
aura::WindowTreeHost* host) {
if (!host)
return false;
ui::DomCode domcodex = ui::DomCode::NONE;
ui::DomCode domcodey = ui::DomCode::NONE;
if (swipe_direction & kCursorMoveRight)
domcodex = ui::DomCode::ARROW_RIGHT;
else if (swipe_direction & kCursorMoveLeft)
domcodex = ui::DomCode::ARROW_LEFT;
if (swipe_direction & kCursorMoveUp)
domcodey = ui::DomCode::ARROW_UP;
else if (swipe_direction & kCursorMoveDown)
domcodey = ui::DomCode::ARROW_DOWN;
// First deal with the x movement.
if (domcodex != ui::DomCode::NONE) {
ui::KeyboardCode codex = ui::VKEY_UNKNOWN;
ui::DomKey domkeyx = ui::DomKey::NONE;
ignore_result(DomCodeToUsLayoutDomKey(domcodex, ui::EF_NONE, &domkeyx,
&codex));
ui::KeyEvent press_event(ui::ET_KEY_PRESSED, codex, domcodex,
modifier_flags, domkeyx,
ui::EventTimeForNow());
ui::EventDispatchDetails details =
host->event_processor()->OnEventFromSource(&press_event);
CHECK(!details.dispatcher_destroyed);
ui::KeyEvent release_event(ui::ET_KEY_RELEASED, codex, domcodex,
modifier_flags, domkeyx,
ui::EventTimeForNow());
details = host->event_processor()->OnEventFromSource(&release_event);
CHECK(!details.dispatcher_destroyed);
}
// Then deal with the y movement.
if (domcodey != ui::DomCode::NONE) {
ui::KeyboardCode codey = ui::VKEY_UNKNOWN;
ui::DomKey domkeyy = ui::DomKey::NONE;
ignore_result(DomCodeToUsLayoutDomKey(domcodey, ui::EF_NONE, &domkeyy,
&codey));
ui::KeyEvent press_event(ui::ET_KEY_PRESSED, codey, domcodey,
modifier_flags, domkeyy,
ui::EventTimeForNow());
ui::EventDispatchDetails details =
host->event_processor()->OnEventFromSource(&press_event);
CHECK(!details.dispatcher_destroyed);
ui::KeyEvent release_event(ui::ET_KEY_RELEASED, codey, domcodey,
modifier_flags, domkeyy,
ui::EventTimeForNow());
details = host->event_processor()->OnEventFromSource(&release_event);
CHECK(!details.dispatcher_destroyed);
}
return true;
}
bool SendKeyEvent(const std::string type,
int key_value,
int key_code,
std::string key_name,
int modifiers,
aura::WindowTreeHost* host) {
ui::EventType event_type = ui::ET_UNKNOWN;
if (type == kKeyDown)
event_type = ui::ET_KEY_PRESSED;
else if (type == kKeyUp)
event_type = ui::ET_KEY_RELEASED;
if (event_type == ui::ET_UNKNOWN)
return false;
ui::KeyboardCode code = static_cast<ui::KeyboardCode>(key_code);
ui::InputMethod* input_method = host->GetInputMethod();
if (code == ui::VKEY_UNKNOWN) {
// Handling of special printable characters (e.g. accented characters) for
// which there is no key code.
if (event_type == ui::ET_KEY_RELEASED) {
if (!input_method)
return false;
ui::TextInputClient* tic = input_method->GetTextInputClient();
SendProcessKeyEvent(ui::ET_KEY_PRESSED, host);
tic->InsertChar(static_cast<uint16>(key_value), ui::EF_NONE);
SendProcessKeyEvent(ui::ET_KEY_RELEASED, host);
}
} else {
if (event_type == ui::ET_KEY_RELEASED) {
// The number of key press events seen since the last backspace.
static int keys_seen = 0;
if (code == ui::VKEY_BACK) {
// Log the rough lengths of characters typed between backspaces. This
// metric will be used to determine the error rate for the keyboard.
UMA_HISTOGRAM_CUSTOM_COUNTS(
"VirtualKeyboard.KeystrokesBetweenBackspaces",
keys_seen, 1, 1000, 50);
keys_seen = 0;
} else {
++keys_seen;
}
}
ui::DomCode dom_code = ui::DomCode::NONE;
if (!key_name.empty())
dom_code = ui::KeycodeConverter::CodeStringToDomCode(key_name.c_str());
if (dom_code == ui::DomCode::NONE)
dom_code = ui::UsLayoutKeyboardCodeToDomCode(code);
CHECK(dom_code != ui::DomCode::NONE);
ui::KeyEvent event(
event_type,
code,
dom_code,
modifiers);
if (input_method) {
input_method->DispatchKeyEvent(&event);
} else {
ui::EventDispatchDetails details =
host->event_processor()->OnEventFromSource(&event);
CHECK(!details.dispatcher_destroyed);
}
}
return true;
}
void MarkKeyboardLoadStarted() {
if (!g_keyboard_load_time_start.Get().ToInternalValue())
g_keyboard_load_time_start.Get() = base::Time::Now();
}
void MarkKeyboardLoadFinished() {
// Possible to get a load finished without a start if navigating directly to
// chrome://keyboard.
if (!g_keyboard_load_time_start.Get().ToInternalValue())
return;
// It should not be possible to finish loading the keyboard without starting
// to load it first.
DCHECK(g_keyboard_load_time_start.Get().ToInternalValue());
static bool logged = false;
if (!logged) {
// Log the delta only once.
UMA_HISTOGRAM_TIMES(
"VirtualKeyboard.FirstLoadTime",
base::Time::Now() - g_keyboard_load_time_start.Get());
logged = true;
}
}
const GritResourceMap* GetKeyboardExtensionResources(size_t* size) {
// This looks a lot like the contents of a resource map; however it is
// necessary to have a custom path for the extension path, so the resource
// map cannot be used directly.
static const GritResourceMap kKeyboardResources[] = {
{"keyboard/locales/en.js", IDR_KEYBOARD_LOCALES_EN},
{"keyboard/config/emoji.js", IDR_KEYBOARD_CONFIG_EMOJI},
{"keyboard/config/hwt.js", IDR_KEYBOARD_CONFIG_HWT},
{"keyboard/config/us.js", IDR_KEYBOARD_CONFIG_US},
{"keyboard/emoji.css", IDR_KEYBOARD_CSS_EMOJI},
{"keyboard/images/3dots.png", IDR_KEYBOARD_IMAGES_3_DOTS},
{"keyboard/images/back_to_keyboard.png",
IDR_KEYBOARD_IMAGES_BACK_TO_KEYBOARD},
{"keyboard/images/backspace.png", IDR_KEYBOARD_IMAGES_BACKSPACE},
{"keyboard/images/car.png", IDR_KEYBOARD_IMAGES_CAR},
{"keyboard/images/check.png", IDR_KEYBOARD_IMAGES_CHECK},
{"keyboard/images/check_in_menu.png", IDR_KEYBOARD_IMAGES_CHECK_IN_MENU},
{"keyboard/images/compact.png", IDR_KEYBOARD_IMAGES_COMPACT},
{"keyboard/images/down.png", IDR_KEYBOARD_IMAGES_DOWN},
{"keyboard/images/emoji.png", IDR_KEYBOARD_IMAGES_EMOJI},
{"keyboard/images/emoji_car.png", IDR_KEYBOARD_IMAGES_EMOJI_CAR},
{"keyboard/images/emoji_crown.png", IDR_KEYBOARD_IMAGES_EMOJI_CROWN},
{"keyboard/images/emoji_emoticon.png",
IDR_KEYBOARD_IMAGES_EMOJI_EMOTICON},
{"keyboard/images/emoji_flower.png", IDR_KEYBOARD_IMAGES_EMOJI_FLOWER},
{"keyboard/images/emoji_hot.png", IDR_KEYBOARD_IMAGES_EMOJI_HOT},
{"keyboard/images/emoji_recent.png", IDR_KEYBOARD_IMAGES_EMOJI_RECENT},
{"keyboard/images/emoji_shape.png", IDR_KEYBOARD_IMAGES_EMOJI_SHAPE},
{"keyboard/images/emoji_cat_items.png", IDR_KEYBOARD_IMAGES_CAT},
{"keyboard/images/emoticon.png", IDR_KEYBOARD_IMAGES_EMOTICON},
{"keyboard/images/enter.png", IDR_KEYBOARD_IMAGES_RETURN},
{"keyboard/images/error.png", IDR_KEYBOARD_IMAGES_ERROR},
{"keyboard/images/favorit.png", IDR_KEYBOARD_IMAGES_FAVORITE},
{"keyboard/images/flower.png", IDR_KEYBOARD_IMAGES_FLOWER},
{"keyboard/images/globe.png", IDR_KEYBOARD_IMAGES_GLOBE},
{"keyboard/images/hide.png", IDR_KEYBOARD_IMAGES_HIDE},
{"keyboard/images/hidekeyboard.png", IDR_KEYBOARD_IMAGES_HIDE_KEYBOARD},
{"keyboard/images/keyboard.svg", IDR_KEYBOARD_IMAGES_KEYBOARD},
{"keyboard/images/left.png", IDR_KEYBOARD_IMAGES_LEFT},
{"keyboard/images/penci.png", IDR_KEYBOARD_IMAGES_PENCIL},
{"keyboard/images/recent.png", IDR_KEYBOARD_IMAGES_RECENT},
{"keyboard/images/regular_size.png", IDR_KEYBOARD_IMAGES_FULLSIZE},
{"keyboard/images/menu.png", IDR_KEYBOARD_IMAGES_MENU},
{"keyboard/images/pencil.png", IDR_KEYBOARD_IMAGES_PENCIL},
{"keyboard/images/right.png", IDR_KEYBOARD_IMAGES_RIGHT},
{"keyboard/images/search.png", IDR_KEYBOARD_IMAGES_SEARCH},
{"keyboard/images/select_right.png", IDR_KEYBOARD_IMAGES_SELECT_RIGHT},
{"keyboard/images/select_left.png", IDR_KEYBOARD_IMAGES_SELECT_LEFT},
{"keyboard/images/setting.png", IDR_KEYBOARD_IMAGES_SETTINGS},
{"keyboard/images/shift.png", IDR_KEYBOARD_IMAGES_SHIFT},
{"keyboard/images/space.png", IDR_KEYBOARD_IMAGES_SPACE},
{"keyboard/images/tab.png", IDR_KEYBOARD_IMAGES_TAB},
{"keyboard/images/tab_in_fullsize.png",
IDR_KEYBOARD_IMAGES_TAB_IN_FULLSIZE},
{"keyboard/images/triangle.png", IDR_KEYBOARD_IMAGES_TRIANGLE},
{"keyboard/images/up.png", IDR_KEYBOARD_IMAGES_UP},
{"keyboard/index.html", IDR_KEYBOARD_INDEX},
{"keyboard/inputview_adapter.js", IDR_KEYBOARD_INPUTVIEW_ADAPTER},
{"keyboard/inputview.css", IDR_KEYBOARD_INPUTVIEW_CSS},
{"keyboard/inputview.js", IDR_KEYBOARD_INPUTVIEW_JS},
{"keyboard/inputview_layouts/101kbd.js", IDR_KEYBOARD_LAYOUTS_101},
{"keyboard/inputview_layouts/compactkbd-qwerty.js",
IDR_KEYBOARD_LAYOUTS_COMPACT_QWERTY},
{"keyboard/inputview_layouts/compactkbd-numberpad.js",
IDR_KEYBOARD_LAYOUTS_COMPACT_NUMBERPAD},
{"keyboard/inputview_layouts/emoji.js", IDR_KEYBOARD_LAYOUTS_EMOJI},
{"keyboard/inputview_layouts/handwriting.js", IDR_KEYBOARD_LAYOUTS_HWT},
{"keyboard/manifest.json", IDR_KEYBOARD_MANIFEST},
{"keyboard/sounds/keypress-delete.wav",
IDR_KEYBOARD_SOUNDS_KEYPRESS_DELETE},
{"keyboard/sounds/keypress-return.wav",
IDR_KEYBOARD_SOUNDS_KEYPRESS_RETURN},
{"keyboard/sounds/keypress-spacebar.wav",
IDR_KEYBOARD_SOUNDS_KEYPRESS_SPACEBAR},
{"keyboard/sounds/keypress-standard.wav",
IDR_KEYBOARD_SOUNDS_KEYPRESS_STANDARD},
};
static const size_t kKeyboardResourcesSize = arraysize(kKeyboardResources);
*size = kKeyboardResourcesSize;
return kKeyboardResources;
}
void SetOverrideContentUrl(const GURL& url) {
g_override_content_url.Get() = url;
}
const GURL& GetOverrideContentUrl() {
return g_override_content_url.Get();
}
void LogKeyboardControlEvent(KeyboardControlEvent event) {
UMA_HISTOGRAM_ENUMERATION(
"VirtualKeyboard.KeyboardControlEvent",
event,
keyboard::KEYBOARD_CONTROL_MAX);
}
} // namespace keyboard