Commit f84b652f authored by David Tseng's avatar David Tseng Committed by Commit Bot

Support programmatic edit key commands

- introduce a scoped setter which enables the keyboard during spoken
feedback's lifetime
- remove restriction/message from ChromeVox disclaimer asking user to
enable a11y vk
- re-map some Search-like-function keys
1. Search+Left/Right -> Home/End
2. Search+Shift+Left/Right -> Shift Home/End
3. Search+Ctrl+Left/Right -> Ctrl+Home/End
4. Search+Shift+Ctrl+Left/Right -> Shift+Ctrl+Home/End
- re-map braille commands to edit key commands as follows:
1. dot 3/6 chord -> previous/nextCharacter -> Left/Right
2. dot 2/5 chord -> previous/nextWord -> Ctrl+Left/Right
3. dot 1/4 chord -> previous/nextObject -> Up/Down
4. dot 2-3/5-6 chord -> previous/nextGroup -> Ctrl+Up/Down

(jumpToTop/Bottom remain dots 1-2-3/4-5-6 and trigger ordinary edit key command
mappings).

notes:
we use the vk codepath because it handles compatibility down the stack
for us and potentially triggers other side effects such as auto
complete/IME

Test: with a braille display, ensure a11y vk off. Press incrementally
Ctrl + t; verify new tab shows up.  In Google Docs, press
Search+Left/Right (and all other combos enumerated above). Verify proper
caret/selection afterwards. Turn on sticky mode. Verify Search nav
performs ChromeVox commands rather than send keys.

Do all of the same with a braille display inside of Docs. Verify ability
to edit and navigate directly from display.

Bug: 
Cq-Include-Trybots: master.tryserver.chromium.linux:closure_compilation
Change-Id: I4878c8a930cd643c406294b7f302c9ad2c07ee9f
Reviewed-on: https://chromium-review.googlesource.com/823578
Commit-Queue: David Tseng <dtseng@chromium.org>
Reviewed-by: default avatarDominic Mazzoni <dmazzoni@chromium.org>
Cr-Commit-Position: refs/heads/master@{#524545}
parent 58bea862
...@@ -149,6 +149,23 @@ class ChromeVoxPanelWidgetObserver : public views::WidgetObserver { ...@@ -149,6 +149,23 @@ class ChromeVoxPanelWidgetObserver : public views::WidgetObserver {
DISALLOW_COPY_AND_ASSIGN(ChromeVoxPanelWidgetObserver); DISALLOW_COPY_AND_ASSIGN(ChromeVoxPanelWidgetObserver);
}; };
class ScopedKeyboardStateSetter {
public:
ScopedKeyboardStateSetter() : is_enabled_(keyboard::IsKeyboardEnabled()) {
keyboard::SetRequestedKeyboardState(keyboard::KEYBOARD_STATE_ENABLED);
}
~ScopedKeyboardStateSetter() {
if (!is_enabled_)
keyboard::SetRequestedKeyboardState(keyboard::KEYBOARD_STATE_DISABLED);
}
private:
bool is_enabled_;
DISALLOW_COPY_AND_ASSIGN(ScopedKeyboardStateSetter);
};
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
// AccessibilityStatusEventDetails // AccessibilityStatusEventDetails
...@@ -1459,6 +1476,8 @@ void AccessibilityManager::PostLoadChromeVox() { ...@@ -1459,6 +1476,8 @@ void AccessibilityManager::PostLoadChromeVox() {
base::CommandLine::ForCurrentProcess()->AppendSwitch( base::CommandLine::ForCurrentProcess()->AppendSwitch(
::switches::kEnableAudioFocus); ::switches::kEnableAudioFocus);
} }
keyboard_state_setter_.reset(new ScopedKeyboardStateSetter());
} }
void AccessibilityManager::PostUnloadChromeVox() { void AccessibilityManager::PostUnloadChromeVox() {
...@@ -1477,6 +1496,8 @@ void AccessibilityManager::PostUnloadChromeVox() { ...@@ -1477,6 +1496,8 @@ void AccessibilityManager::PostUnloadChromeVox() {
// In case the user darkened the screen, undarken it now. // In case the user darkened the screen, undarken it now.
scoped_backlights_forced_off_.reset(); scoped_backlights_forced_off_.reset();
keyboard_state_setter_.reset();
} }
void AccessibilityManager::PostSwitchChromeVoxProfile() { void AccessibilityManager::PostSwitchChromeVoxProfile() {
......
...@@ -41,6 +41,7 @@ namespace chromeos { ...@@ -41,6 +41,7 @@ namespace chromeos {
class AccessibilityExtensionLoader; class AccessibilityExtensionLoader;
class AccessibilityHighlightManager; class AccessibilityHighlightManager;
class ScopedKeyboardStateSetter;
class SelectToSpeakEventHandler; class SelectToSpeakEventHandler;
class SwitchAccessEventHandler; class SwitchAccessEventHandler;
...@@ -449,6 +450,8 @@ class AccessibilityManager ...@@ -449,6 +450,8 @@ class AccessibilityManager
// Used to force the backlights off to darken the screen. // Used to force the backlights off to darken the screen.
std::unique_ptr<ash::ScopedBacklightsForcedOff> scoped_backlights_forced_off_; std::unique_ptr<ash::ScopedBacklightsForcedOff> scoped_backlights_forced_off_;
std::unique_ptr<ScopedKeyboardStateSetter> keyboard_state_setter_;
base::WeakPtrFactory<AccessibilityManager> weak_ptr_factory_; base::WeakPtrFactory<AccessibilityManager> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(AccessibilityManager); DISALLOW_COPY_AND_ASSIGN(AccessibilityManager);
......
...@@ -421,14 +421,6 @@ cvox.BrailleInputHandler.prototype = { ...@@ -421,14 +421,6 @@ cvox.BrailleInputHandler.prototype = {
chrome.virtualKeyboardPrivate.getKeyboardConfig(function(config) { chrome.virtualKeyboardPrivate.getKeyboardConfig(function(config) {
// Use the virtual keyboard API instead of the IME key event API // Use the virtual keyboard API instead of the IME key event API
// so that these keys work even if the Braille IME is not active. // so that these keys work even if the Braille IME is not active.
// The virtual keyboard private api fails silently if the a11y keyboard
// isn't enabled in settings. Let the user know.
if (!config.a11ymode) {
new Output().format('@enable_virtual_keyboard').go();
return;
}
var keyName = /** @type {string} */ (event.standardKeyCode); var keyName = /** @type {string} */ (event.standardKeyCode);
var numericCode = cvox.BrailleKeyEvent.keyCodeToLegacyCode(keyName); var numericCode = cvox.BrailleKeyEvent.keyCodeToLegacyCode(keyName);
if (!goog.isDef(numericCode)) if (!goog.isDef(numericCode))
......
...@@ -348,8 +348,13 @@ Background.prototype = { ...@@ -348,8 +348,13 @@ Background.prototype = {
return false; return false;
var command = BrailleCommandHandler.getCommand(evt.brailleDots); var command = BrailleCommandHandler.getCommand(evt.brailleDots);
if (command) if (command) {
CommandHandler.onCommand(command); if (!ChromeVoxState.instance.currentRange ||
!ChromeVoxState.instance.currentRange.start.node
.state[StateType.EDITABLE] ||
BrailleCommandHandler.onEditCommand(command))
CommandHandler.onCommand(command);
}
break; break;
default: default:
return false; return false;
......
...@@ -449,6 +449,10 @@ TEST_F('BackgroundTest', 'EarconsForControls', function() { ...@@ -449,6 +449,10 @@ TEST_F('BackgroundTest', 'EarconsForControls', function() {
.call(doCmd('nextObject')) .call(doCmd('nextObject'))
.expectSpeech('Edit text') .expectSpeech('Edit text')
.expectEarcon(cvox.Earcon.EDITABLE_TEXT) .expectEarcon(cvox.Earcon.EDITABLE_TEXT)
// Editable text Search re-mappings are in effect.
.call(doCmd('toggleStickyMode'))
.expectSpeech('Sticky mode enabled')
.call(doCmd('nextObject')) .call(doCmd('nextObject'))
.expectSpeech('List box') .expectSpeech('List box')
.expectEarcon(cvox.Earcon.LISTBOX) .expectEarcon(cvox.Earcon.LISTBOX)
......
...@@ -8,7 +8,11 @@ ...@@ -8,7 +8,11 @@
goog.provide('BrailleCommandHandler'); goog.provide('BrailleCommandHandler');
goog.require('BackgroundKeyboardHandler');
goog.scope(function() { goog.scope(function() {
var Mod = constants.ModifierFlag;
/** /**
* Maps a dot pattern to a command. * Maps a dot pattern to a command.
* @type {!Object<number, string>} * @type {!Object<number, string>}
...@@ -87,6 +91,46 @@ BrailleCommandHandler.getDots = function(command) { ...@@ -87,6 +91,46 @@ BrailleCommandHandler.getDots = function(command) {
return 0; return 0;
}; };
/**
* Customizes ChromeVox commands when issued from a braille display while within
* editable text.
* @param {string} command
* @return {boolean} True if the command should propagate.
*/
BrailleCommandHandler.onEditCommand = function(command) {
switch (command) {
case 'previousCharacter':
BackgroundKeyboardHandler.sendKeyPress(37, 'ArrowLeft');
break;
case 'nextCharacter':
BackgroundKeyboardHandler.sendKeyPress(39, 'ArrowRight');
break;
case 'previousWord':
BackgroundKeyboardHandler.sendKeyPress(37, 'ArrowLeft', Mod.CONTROL);
break;
case 'nextWord':
BackgroundKeyboardHandler.sendKeyPress(39, 'ArrowRight', Mod.CONTROL);
break;
case 'previousObject':
case 'previousLine':
BackgroundKeyboardHandler.sendKeyPress(38, 'ArrowUp');
break;
case 'nextObject':
case 'nextLine':
BackgroundKeyboardHandler.sendKeyPress(40, 'ArrowDown');
break;
case 'previousGroup':
BackgroundKeyboardHandler.sendKeyPress(38, 'ArrowUp', Mod.CONTROL);
break;
case 'nextGroup':
BackgroundKeyboardHandler.sendKeyPress(40, 'ArrowDown', Mod.CONTROL);
break;
default:
return true;
}
return false;
};
/** /**
* @private * @private
*/ */
......
...@@ -18,8 +18,10 @@ goog.scope(function() { ...@@ -18,8 +18,10 @@ goog.scope(function() {
var AutomationEvent = chrome.automation.AutomationEvent; var AutomationEvent = chrome.automation.AutomationEvent;
var AutomationNode = chrome.automation.AutomationNode; var AutomationNode = chrome.automation.AutomationNode;
var Dir = constants.Dir; var Dir = constants.Dir;
var Mod = constants.ModifierFlag;
var EventType = chrome.automation.EventType; var EventType = chrome.automation.EventType;
var RoleType = chrome.automation.RoleType; var RoleType = chrome.automation.RoleType;
var StateType = chrome.automation.StateType;
/** /**
* Handles ChromeVox Next commands. * Handles ChromeVox Next commands.
...@@ -238,6 +240,11 @@ CommandHandler.onCommand = function(command) { ...@@ -238,6 +240,11 @@ CommandHandler.onCommand = function(command) {
return true; return true;
var current = ChromeVoxState.instance.currentRange_; var current = ChromeVoxState.instance.currentRange_;
// Allow edit commands first.
if (!CommandHandler.onEditCommand_(current, command))
return false;
var dir = Dir.FORWARD; var dir = Dir.FORWARD;
var pred = null; var pred = null;
var predErrorMsg = undefined; var predErrorMsg = undefined;
...@@ -903,6 +910,58 @@ CommandHandler.viewGraphicAsBraille_ = function(current) { ...@@ -903,6 +910,58 @@ CommandHandler.viewGraphicAsBraille_ = function(current) {
} }
}; };
/**
* Provides a partial mapping from ChromeVox key combinations to
* Search-as-a-function key as seen in Chrome OS documentation.
* @param {cursors.Range} current
* @param {string} command
* @return {boolean} True if the command should propagate.
* @private
*/
CommandHandler.onEditCommand_ = function(current, command) {
if (cvox.ChromeVox.isStickyModeOn() || !current || !current.start ||
!current.start.node || !current.start.node.state[StateType.EDITABLE])
return true;
switch (command) {
case 'previousCharacter':
BackgroundKeyboardHandler.sendKeyPress(36, 'Home', Mod.SHIFT);
break;
case 'nextCharacter':
BackgroundKeyboardHandler.sendKeyPress(35, 'End', Mod.SHIFT);
break;
case 'previousWord':
BackgroundKeyboardHandler.sendKeyPress(
36, 'Home', Mod.SHIFT | Mod.CONTROL);
break;
case 'nextWord':
BackgroundKeyboardHandler.sendKeyPress(
35, 'End', Mod.SHIFT | Mod.CONTROL);
break;
case 'previousObject':
BackgroundKeyboardHandler.sendKeyPress(36, 'Home');
break;
case 'nextObject':
BackgroundKeyboardHandler.sendKeyPress(35, 'End');
break;
case 'previousLine':
BackgroundKeyboardHandler.sendKeyPress(33, 'PageUp');
break;
case 'nextLine':
BackgroundKeyboardHandler.sendKeyPress(34, 'PageDown');
break;
case 'jumpToTop':
BackgroundKeyboardHandler.sendKeyPress(36, 'Home', Mod.CONTROL);
break;
case 'jumpToBottom':
BackgroundKeyboardHandler.sendKeyPress(35, 'End', Mod.CONTROL);
break;
default:
return true;
}
return false;
};
/** /**
* Performs global initialization. * Performs global initialization.
*/ */
......
...@@ -30,3 +30,14 @@ constants.Dir = { ...@@ -30,3 +30,14 @@ constants.Dir = {
* @const * @const
*/ */
constants.OBJECT_MAX_CHARCOUNT = 1500; constants.OBJECT_MAX_CHARCOUNT = 1500;
/**
* Modifier values used by Chrome.
* See ui/events/event_constants.h
*/
constants.ModifierFlag = {
SHIFT: 2,
CONTROL: 4,
ALT: 8,
SEARCH: 16
};
...@@ -8,7 +8,10 @@ ...@@ -8,7 +8,10 @@
goog.provide('BackgroundKeyboardHandler'); goog.provide('BackgroundKeyboardHandler');
goog.require('ChromeVoxState');
goog.require('Output');
goog.require('cvox.ChromeVoxKbHandler'); goog.require('cvox.ChromeVoxKbHandler');
goog.require('cvox.ChromeVoxPrefs');
/** @constructor */ /** @constructor */
BackgroundKeyboardHandler = function() { BackgroundKeyboardHandler = function() {
...@@ -72,3 +75,24 @@ BackgroundKeyboardHandler.prototype = { ...@@ -72,3 +75,24 @@ BackgroundKeyboardHandler.prototype = {
return false; return false;
} }
}; };
/**
* @param {number} keyCode
* @param {string} keyName
* @param {number=} modifiers
* @return {boolean}
*/
BackgroundKeyboardHandler.sendKeyPress = function(keyCode, keyName, modifiers) {
modifiers = modifiers || 0;
var key = {
type: 'keydown',
keyCode: keyCode,
keyName: keyName,
charValue: keyCode,
modifiers: modifiers
};
chrome.virtualKeyboardPrivate.sendKeyEvent(key);
key['type'] = 'keyup';
chrome.virtualKeyboardPrivate.sendKeyEvent(key);
return true;
};
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment