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.
5 #include "ui/keyboard/keyboard_util.h"
9 #include "base/basictypes.h"
10 #include "base/command_line.h"
11 #include "base/lazy_instance.h"
12 #include "base/logging.h"
13 #include "base/metrics/histogram.h"
14 #include "base/strings/string16.h"
15 #include "grit/keyboard_resources.h"
16 #include "grit/keyboard_resources_map.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/events/event_utils.h"
22 #include "ui/events/keycodes/dom/dom_code.h"
23 #include "ui/events/keycodes/dom/dom_key.h"
24 #include "ui/events/keycodes/dom/keycode_converter.h"
25 #include "ui/events/keycodes/keyboard_code_conversion.h"
26 #include "ui/keyboard/keyboard_controller.h"
27 #include "ui/keyboard/keyboard_controller_proxy.h"
28 #include "ui/keyboard/keyboard_switches.h"
33 const char kKeyDown
[] ="keydown";
34 const char kKeyUp
[] = "keyup";
36 void SendProcessKeyEvent(ui::EventType type
,
37 aura::WindowTreeHost
* host
) {
38 ui::KeyEvent
event(type
, ui::VKEY_PROCESSKEY
, ui::DomCode::NONE
,
39 ui::EF_IS_SYNTHESIZED
, ui::DomKey::PROCESS
, 0,
40 ui::EventTimeForNow());
41 ui::EventDispatchDetails details
=
42 host
->event_processor()->OnEventFromSource(&event
);
43 CHECK(!details
.dispatcher_destroyed
);
46 base::LazyInstance
<base::Time
> g_keyboard_load_time_start
=
47 LAZY_INSTANCE_INITIALIZER
;
49 bool g_accessibility_keyboard_enabled
= false;
51 base::LazyInstance
<GURL
> g_override_content_url
= LAZY_INSTANCE_INITIALIZER
;
53 bool g_touch_keyboard_enabled
= false;
55 keyboard::KeyboardOverscrolOverride g_keyboard_overscroll_override
=
56 keyboard::KEYBOARD_OVERSCROLL_OVERRIDE_NONE
;
58 keyboard::KeyboardShowOverride g_keyboard_show_override
=
59 keyboard::KEYBOARD_SHOW_OVERRIDE_NONE
;
65 gfx::Rect
FullWidthKeyboardBoundsFromRootBounds(const gfx::Rect
& root_bounds
,
66 int keyboard_height
) {
69 root_bounds
.bottom() - keyboard_height
,
74 void SetAccessibilityKeyboardEnabled(bool enabled
) {
75 g_accessibility_keyboard_enabled
= enabled
;
78 bool GetAccessibilityKeyboardEnabled() {
79 return g_accessibility_keyboard_enabled
;
82 void SetTouchKeyboardEnabled(bool enabled
) {
83 g_touch_keyboard_enabled
= enabled
;
86 bool GetTouchKeyboardEnabled() {
87 return g_touch_keyboard_enabled
;
90 std::string
GetKeyboardLayout() {
91 // TODO(bshe): layout string is currently hard coded. We should use more
92 // standard keyboard layouts.
93 return GetAccessibilityKeyboardEnabled() ? "system-qwerty" : "qwerty";
96 bool IsKeyboardEnabled() {
97 // Accessibility setting prioritized over policy setting.
98 if (g_accessibility_keyboard_enabled
)
100 // Policy strictly disables showing a virtual keyboard.
101 if (g_keyboard_show_override
== keyboard::KEYBOARD_SHOW_OVERRIDE_DISABLED
)
103 // Check if any of the flags are enabled.
104 return base::CommandLine::ForCurrentProcess()->HasSwitch(
105 switches::kEnableVirtualKeyboard
) ||
106 g_touch_keyboard_enabled
||
107 (g_keyboard_show_override
== keyboard::KEYBOARD_SHOW_OVERRIDE_ENABLED
);
110 bool IsKeyboardOverscrollEnabled() {
111 if (!IsKeyboardEnabled())
114 // Users of the accessibility on-screen keyboard are likely to be using mouse
115 // input, which may interfere with overscrolling.
116 if (g_accessibility_keyboard_enabled
)
119 // If overscroll enabled override is set, use it instead. Currently
120 // login / out-of-box disable keyboard overscroll. http://crbug.com/363635
121 if (g_keyboard_overscroll_override
!= KEYBOARD_OVERSCROLL_OVERRIDE_NONE
) {
122 return g_keyboard_overscroll_override
==
123 KEYBOARD_OVERSCROLL_OVERRIDE_ENABLED
;
126 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
127 switches::kDisableVirtualKeyboardOverscroll
)) {
133 void SetKeyboardOverscrollOverride(KeyboardOverscrolOverride override
) {
134 g_keyboard_overscroll_override
= override
;
137 void SetKeyboardShowOverride(KeyboardShowOverride override
) {
138 g_keyboard_show_override
= override
;
141 bool IsInputViewEnabled() {
142 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
143 switches::kEnableInputView
))
145 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
146 switches::kDisableInputView
))
148 // Default value if no command line flags specified.
152 bool IsExperimentalInputViewEnabled() {
153 return base::CommandLine::ForCurrentProcess()->HasSwitch(
154 switches::kEnableExperimentalInputViewFeatures
);
157 bool IsFloatingVirtualKeyboardEnabled() {
158 std::string floating_virtual_keyboard_switch
=
159 base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
160 switches::kFloatingVirtualKeyboard
);
161 return floating_virtual_keyboard_switch
==
162 switches::kFloatingVirtualKeyboardEnabled
;
165 bool IsGestureTypingEnabled() {
166 std::string keyboard_switch
=
167 base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
168 switches::kGestureTyping
);
169 return keyboard_switch
== switches::kGestureTypingEnabled
;
172 bool IsGestureEditingEnabled() {
173 std::string keyboard_switch
=
174 base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
175 switches::kGestureEditing
);
176 return keyboard_switch
== switches::kGestureEditingEnabled
;
179 bool IsSmartDeployEnabled() {
180 std::string keyboard_switch
=
181 base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
182 switches::kSmartVirtualKeyboard
);
183 return keyboard_switch
!= switches::kSmartVirtualKeyboardDisabled
;
186 bool IsMaterialDesignEnabled() {
187 return !base::CommandLine::ForCurrentProcess()->HasSwitch(
188 switches::kDisableNewMDInputView
);
191 bool IsVoiceInputEnabled() {
192 return !base::CommandLine::ForCurrentProcess()->HasSwitch(
193 switches::kDisableVoiceInput
);
196 bool InsertText(const base::string16
& text
) {
197 keyboard::KeyboardController
* controller
= KeyboardController::GetInstance();
201 ui::InputMethod
* input_method
= controller
->proxy()->GetInputMethod();
205 ui::TextInputClient
* tic
= input_method
->GetTextInputClient();
206 if (!tic
|| tic
->GetTextInputType() == ui::TEXT_INPUT_TYPE_NONE
)
209 tic
->InsertText(text
);
214 // TODO(varunjain): It would be cleaner to have something in the
215 // ui::TextInputClient interface, say MoveCaretInDirection(). The code in
216 // here would get the ui::InputMethod from the root_window, and the
217 // ui::TextInputClient from that (see above in InsertText()).
218 bool MoveCursor(int swipe_direction
,
220 aura::WindowTreeHost
* host
) {
223 ui::DomCode domcodex
= ui::DomCode::NONE
;
224 ui::DomCode domcodey
= ui::DomCode::NONE
;
225 if (swipe_direction
& kCursorMoveRight
)
226 domcodex
= ui::DomCode::ARROW_RIGHT
;
227 else if (swipe_direction
& kCursorMoveLeft
)
228 domcodex
= ui::DomCode::ARROW_LEFT
;
230 if (swipe_direction
& kCursorMoveUp
)
231 domcodey
= ui::DomCode::ARROW_UP
;
232 else if (swipe_direction
& kCursorMoveDown
)
233 domcodey
= ui::DomCode::ARROW_DOWN
;
235 // First deal with the x movement.
236 if (domcodex
!= ui::DomCode::NONE
) {
237 ui::KeyboardCode codex
= ui::VKEY_UNKNOWN
;
238 ui::DomKey domkeyx
= ui::DomKey::NONE
;
240 ignore_result(DomCodeToUsLayoutMeaning(domcodex
, ui::EF_NONE
, &domkeyx
,
242 ui::KeyEvent
press_event(ui::ET_KEY_PRESSED
, codex
, domcodex
,
243 modifier_flags
, domkeyx
, cx
,
244 ui::EventTimeForNow());
245 ui::EventDispatchDetails details
=
246 host
->event_processor()->OnEventFromSource(&press_event
);
247 CHECK(!details
.dispatcher_destroyed
);
248 ui::KeyEvent
release_event(ui::ET_KEY_RELEASED
, codex
, domcodex
,
249 modifier_flags
, domkeyx
, cx
,
250 ui::EventTimeForNow());
251 details
= host
->event_processor()->OnEventFromSource(&release_event
);
252 CHECK(!details
.dispatcher_destroyed
);
255 // Then deal with the y movement.
256 if (domcodey
!= ui::DomCode::NONE
) {
257 ui::KeyboardCode codey
= ui::VKEY_UNKNOWN
;
258 ui::DomKey domkeyy
= ui::DomKey::NONE
;
260 ignore_result(DomCodeToUsLayoutMeaning(domcodey
, ui::EF_NONE
, &domkeyy
,
262 ui::KeyEvent
press_event(ui::ET_KEY_PRESSED
, codey
, domcodey
,
263 modifier_flags
, domkeyy
, cy
,
264 ui::EventTimeForNow());
265 ui::EventDispatchDetails details
=
266 host
->event_processor()->OnEventFromSource(&press_event
);
267 CHECK(!details
.dispatcher_destroyed
);
268 ui::KeyEvent
release_event(ui::ET_KEY_RELEASED
, codey
, domcodey
,
269 modifier_flags
, domkeyy
, cy
,
270 ui::EventTimeForNow());
271 details
= host
->event_processor()->OnEventFromSource(&release_event
);
272 CHECK(!details
.dispatcher_destroyed
);
277 bool SendKeyEvent(const std::string type
,
280 std::string key_name
,
282 aura::WindowTreeHost
* host
) {
283 ui::EventType event_type
= ui::ET_UNKNOWN
;
284 if (type
== kKeyDown
)
285 event_type
= ui::ET_KEY_PRESSED
;
286 else if (type
== kKeyUp
)
287 event_type
= ui::ET_KEY_RELEASED
;
288 if (event_type
== ui::ET_UNKNOWN
)
291 ui::KeyboardCode code
= static_cast<ui::KeyboardCode
>(key_code
);
293 ui::InputMethod
* input_method
= host
->GetInputMethod();
294 if (code
== ui::VKEY_UNKNOWN
) {
295 // Handling of special printable characters (e.g. accented characters) for
296 // which there is no key code.
297 if (event_type
== ui::ET_KEY_RELEASED
) {
301 ui::TextInputClient
* tic
= input_method
->GetTextInputClient();
303 SendProcessKeyEvent(ui::ET_KEY_PRESSED
, host
);
304 tic
->InsertChar(static_cast<uint16
>(key_value
), ui::EF_NONE
);
305 SendProcessKeyEvent(ui::ET_KEY_RELEASED
, host
);
308 if (event_type
== ui::ET_KEY_RELEASED
) {
309 // The number of key press events seen since the last backspace.
310 static int keys_seen
= 0;
311 if (code
== ui::VKEY_BACK
) {
312 // Log the rough lengths of characters typed between backspaces. This
313 // metric will be used to determine the error rate for the keyboard.
314 UMA_HISTOGRAM_CUSTOM_COUNTS(
315 "VirtualKeyboard.KeystrokesBetweenBackspaces",
316 keys_seen
, 1, 1000, 50);
323 ui::DomCode dom_code
= ui::DomCode::NONE
;
324 if (!key_name
.empty())
325 dom_code
= ui::KeycodeConverter::CodeStringToDomCode(key_name
.c_str());
326 if (dom_code
== ui::DomCode::NONE
)
327 dom_code
= ui::UsLayoutKeyboardCodeToDomCode(code
);
328 CHECK(dom_code
!= ui::DomCode::NONE
);
335 input_method
->DispatchKeyEvent(event
);
337 ui::EventDispatchDetails details
=
338 host
->event_processor()->OnEventFromSource(&event
);
339 CHECK(!details
.dispatcher_destroyed
);
345 void MarkKeyboardLoadStarted() {
346 if (!g_keyboard_load_time_start
.Get().ToInternalValue())
347 g_keyboard_load_time_start
.Get() = base::Time::Now();
350 void MarkKeyboardLoadFinished() {
351 // Possible to get a load finished without a start if navigating directly to
352 // chrome://keyboard.
353 if (!g_keyboard_load_time_start
.Get().ToInternalValue())
356 // It should not be possible to finish loading the keyboard without starting
358 DCHECK(g_keyboard_load_time_start
.Get().ToInternalValue());
360 static bool logged
= false;
362 // Log the delta only once.
364 "VirtualKeyboard.FirstLoadTime",
365 base::Time::Now() - g_keyboard_load_time_start
.Get());
370 const GritResourceMap
* GetKeyboardExtensionResources(size_t* size
) {
371 // This looks a lot like the contents of a resource map; however it is
372 // necessary to have a custom path for the extension path, so the resource
373 // map cannot be used directly.
374 static const GritResourceMap kKeyboardResources
[] = {
375 {"keyboard/locales/en.js", IDR_KEYBOARD_LOCALES_EN
},
376 {"keyboard/config/m-emoji.js", IDR_KEYBOARD_CONFIG_EMOJI
},
377 {"keyboard/config/m-hwt.js", IDR_KEYBOARD_CONFIG_HWT
},
378 {"keyboard/config/us.js", IDR_KEYBOARD_CONFIG_US
},
379 {"keyboard/emoji.css", IDR_KEYBOARD_CSS_EMOJI
},
380 {"keyboard/images/backspace.png", IDR_KEYBOARD_IMAGES_BACKSPACE
},
381 {"keyboard/images/car.png", IDR_KEYBOARD_IMAGES_CAR
},
382 {"keyboard/images/check.png", IDR_KEYBOARD_IMAGES_CHECK
},
383 {"keyboard/images/compact.png", IDR_KEYBOARD_IMAGES_COMPACT
},
384 {"keyboard/images/down.png", IDR_KEYBOARD_IMAGES_DOWN
},
385 {"keyboard/images/emoji.png", IDR_KEYBOARD_IMAGES_EMOJI
},
386 {"keyboard/images/emoji_cat_items.png", IDR_KEYBOARD_IMAGES_CAT
},
387 {"keyboard/images/emoticon.png", IDR_KEYBOARD_IMAGES_EMOTICON
},
388 {"keyboard/images/enter.png", IDR_KEYBOARD_IMAGES_RETURN
},
389 {"keyboard/images/error.png", IDR_KEYBOARD_IMAGES_ERROR
},
390 {"keyboard/images/favorit.png", IDR_KEYBOARD_IMAGES_FAVORITE
},
391 {"keyboard/images/flower.png", IDR_KEYBOARD_IMAGES_FLOWER
},
392 {"keyboard/images/globe.png", IDR_KEYBOARD_IMAGES_GLOBE
},
393 {"keyboard/images/hide.png", IDR_KEYBOARD_IMAGES_HIDE_KEYBOARD
},
394 {"keyboard/images/keyboard.svg", IDR_KEYBOARD_IMAGES_KEYBOARD
},
395 {"keyboard/images/left.png", IDR_KEYBOARD_IMAGES_LEFT
},
396 {"keyboard/images/penci.png", IDR_KEYBOARD_IMAGES_PENCIL
},
397 {"keyboard/images/recent.png", IDR_KEYBOARD_IMAGES_RECENT
},
398 {"keyboard/images/regular_size.png", IDR_KEYBOARD_IMAGES_FULLSIZE
},
399 {"keyboard/images/menu.png", IDR_KEYBOARD_IMAGES_MENU
},
400 {"keyboard/images/pencil.png", IDR_KEYBOARD_IMAGES_PENCIL
},
401 {"keyboard/images/right.png", IDR_KEYBOARD_IMAGES_RIGHT
},
402 {"keyboard/images/search.png", IDR_KEYBOARD_IMAGES_SEARCH
},
403 {"keyboard/images/setting.png", IDR_KEYBOARD_IMAGES_SETTINGS
},
404 {"keyboard/images/shift.png", IDR_KEYBOARD_IMAGES_SHIFT
},
405 {"keyboard/images/space.png", IDR_KEYBOARD_IMAGES_SPACE
},
406 {"keyboard/images/tab.png", IDR_KEYBOARD_IMAGES_TAB
},
407 {"keyboard/images/triangle.png", IDR_KEYBOARD_IMAGES_TRIANGLE
},
408 {"keyboard/images/up.png", IDR_KEYBOARD_IMAGES_UP
},
409 {"keyboard/index.html", IDR_KEYBOARD_INDEX
},
410 {"keyboard/inputview_adapter.js", IDR_KEYBOARD_INPUTVIEW_ADAPTER
},
411 {"keyboard/inputview.css", IDR_KEYBOARD_INPUTVIEW_CSS
},
412 {"keyboard/inputview.js", IDR_KEYBOARD_INPUTVIEW_JS
},
413 {"keyboard/inputview_layouts/101kbd.js", IDR_KEYBOARD_LAYOUTS_101
},
414 {"keyboard/inputview_layouts/compactkbd-qwerty.js",
415 IDR_KEYBOARD_LAYOUTS_COMPACT_QWERTY
},
416 {"keyboard/inputview_layouts/compactkbd-numberpad.js",
417 IDR_KEYBOARD_LAYOUTS_COMPACT_NUMBERPAD
},
418 {"keyboard/inputview_layouts/emoji.js", IDR_KEYBOARD_LAYOUTS_EMOJI
},
419 {"keyboard/inputview_layouts/handwriting.js", IDR_KEYBOARD_LAYOUTS_HWT
},
420 {"keyboard/inputview_layouts/m-101kbd.js",
421 IDR_KEYBOARD_LAYOUTS_MATERIAL_101
},
422 {"keyboard/inputview_layouts/m-compactkbd-qwerty.js",
423 IDR_KEYBOARD_LAYOUTS_MATERIAL_COMPACT_QWERTY
},
424 {"keyboard/inputview_layouts/m-compactkbd-numberpad.js",
425 IDR_KEYBOARD_LAYOUTS_MATERIAL_COMPACT_NUMBERPAD
},
426 {"keyboard/inputview_layouts/m-emoji.js",
427 IDR_KEYBOARD_LAYOUTS_MATERIAL_EMOJI
},
428 {"keyboard/inputview_layouts/m-handwriting.js",
429 IDR_KEYBOARD_LAYOUTS_MATERIAL_HWT
},
430 {"keyboard/manifest.json", IDR_KEYBOARD_MANIFEST
},
431 {"keyboard/sounds/keypress-delete.wav",
432 IDR_KEYBOARD_SOUNDS_KEYPRESS_DELETE
},
433 {"keyboard/sounds/keypress-return.wav",
434 IDR_KEYBOARD_SOUNDS_KEYPRESS_RETURN
},
435 {"keyboard/sounds/keypress-spacebar.wav",
436 IDR_KEYBOARD_SOUNDS_KEYPRESS_SPACEBAR
},
437 {"keyboard/sounds/keypress-standard.wav",
438 IDR_KEYBOARD_SOUNDS_KEYPRESS_STANDARD
},
440 static const size_t kKeyboardResourcesSize
= arraysize(kKeyboardResources
);
441 *size
= kKeyboardResourcesSize
;
442 return kKeyboardResources
;
445 void SetOverrideContentUrl(const GURL
& url
) {
446 g_override_content_url
.Get() = url
;
449 const GURL
& GetOverrideContentUrl() {
450 return g_override_content_url
.Get();
453 void LogKeyboardControlEvent(KeyboardControlEvent event
) {
454 UMA_HISTOGRAM_ENUMERATION(
455 "VirtualKeyboard.KeyboardControlEvent",
457 keyboard::KEYBOARD_CONTROL_MAX
);
460 } // namespace keyboard