Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
This commit is contained in:
parent
93e3a48004
commit
b38dea0597
25901 changed files with 9982925 additions and 369 deletions
364
gaia/apps/system/js/keyboard_manager.js
Executable file
364
gaia/apps/system/js/keyboard_manager.js
Executable file
|
|
@ -0,0 +1,364 @@
|
|||
'use strict';
|
||||
|
||||
/* global IMESwitcher, ImeMenu, KeyboardHelper, inputWindowManager,
|
||||
InputLayouts, LazyLoader, DynamicInputRegistry,
|
||||
InputWindowManager */
|
||||
|
||||
/**
|
||||
* For some flow diagrams related to input management, please refer to
|
||||
* https://wiki.mozilla.org/Gaia/System/InputManagement#Flow_Diagrams .
|
||||
*/
|
||||
|
||||
// These input types are handled by the value selector for now.
|
||||
const VALUE_SELECTOR_INPUT_TYPES = [
|
||||
'select-one', 'select-multiple', 'date',
|
||||
'time', 'datetime', 'datetime-local'];
|
||||
|
||||
const TYPE_GROUP_MAPPING = {
|
||||
// text
|
||||
'text': 'text',
|
||||
'textarea': 'text',
|
||||
'url': 'url',
|
||||
'email': 'email',
|
||||
'password': 'password',
|
||||
'search': 'text',
|
||||
// number
|
||||
'number': 'number',
|
||||
'tel': 'number',
|
||||
// option
|
||||
'select-one': 'option',
|
||||
'select-multiple': 'option',
|
||||
'time': 'option',
|
||||
'week': 'option',
|
||||
'month': 'option',
|
||||
'date': 'option',
|
||||
'datetime': 'option',
|
||||
'datetime-local': 'option',
|
||||
'color': 'option'
|
||||
};
|
||||
|
||||
// How long to wait before we actually switch layouts
|
||||
const SWITCH_CHANGE_DELAY = 20;
|
||||
|
||||
window.KeyboardManager = {
|
||||
// this info keeps the current keyboard's input type group; the string serves
|
||||
// as a reference to access inputLayouts.layouts[group] for current layout's
|
||||
// index and the layout.
|
||||
// when we don't have a current keyboard, this is set to null
|
||||
_showingInputGroup: null,
|
||||
|
||||
_switchChangeTimeout: 0,
|
||||
_onDebug: false,
|
||||
_debug: function km_debug(msg) {
|
||||
if (this._onDebug) {
|
||||
console.log('[Keyboard Manager] ' + msg);
|
||||
}
|
||||
},
|
||||
|
||||
start: function() {
|
||||
// Desktop: physical input remains native while virtual IME is being ported.
|
||||
if (!navigator.mozInputMethod) return Promise.resolve();
|
||||
window.addEventListener('keyboardhide', this);
|
||||
|
||||
navigator.mozInputMethod.mgmt.addEventListener('inputcontextfocus', this);
|
||||
navigator.mozInputMethod.mgmt.addEventListener('inputcontextblur', this);
|
||||
navigator.mozInputMethod.mgmt.addEventListener('showallrequest', this);
|
||||
navigator.mozInputMethod.mgmt.addEventListener('nextrequest', this);
|
||||
|
||||
return LazyLoader.load([
|
||||
'js/ime_switcher.js',
|
||||
'js/input_window_manager.js',
|
||||
'js/input_layouts.js',
|
||||
'js/dynamic_input_registry.js',
|
||||
'shared/js/input_mgmt/input_app_list.js',
|
||||
'shared/js/keyboard_helper.js'
|
||||
]).then(() => {
|
||||
window.inputWindowManager = new InputWindowManager();
|
||||
window.inputWindowManager.start();
|
||||
this.inputLayouts = new InputLayouts(this, TYPE_GROUP_MAPPING);
|
||||
this.inputLayouts.start();
|
||||
this.imeSwitcher = new IMESwitcher();
|
||||
this.imeSwitcher.ontap = this._showImeMenu.bind(this);
|
||||
this.imeSwitcher.start();
|
||||
// Defer the loading of DynamicInputRegistry only after
|
||||
// KeyboardHelper is present. Not that is possible we could miss some
|
||||
// add/removeinputrequest because of this but let's not deal with that
|
||||
// kind of extreme case.
|
||||
this.dynamicInputRegistry = new DynamicInputRegistry();
|
||||
this.dynamicInputRegistry.start();
|
||||
|
||||
KeyboardHelper.watchLayouts(
|
||||
{ enabled: true }, this._updateLayouts.bind(this)
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
_tryLaunchOnBoot: function km_launchOnBoot() {
|
||||
if (inputWindowManager.getLoadedManifestURLs().length) {
|
||||
// There are already keyboard(s) being launched. We don't really care
|
||||
// if a default keyboard should be launch-on-boot.
|
||||
return;
|
||||
}
|
||||
|
||||
var LAUNCH_ON_BOOT_KEY = 'keyboard.launch-on-boot';
|
||||
var req = navigator.mozSettings.createLock().get(LAUNCH_ON_BOOT_KEY);
|
||||
req.onsuccess = req.onerror = (function() {
|
||||
// If the value is not set or it is set to true,
|
||||
// launch the keyboad in background
|
||||
var launchOnBoot = req.result && req.result[LAUNCH_ON_BOOT_KEY];
|
||||
if (typeof launchOnBoot !== 'boolean') {
|
||||
launchOnBoot = true;
|
||||
}
|
||||
|
||||
// if there are still no keyboards running at this point -
|
||||
// set text to show, but don't bring it to the foreground.
|
||||
if (launchOnBoot &&
|
||||
!inputWindowManager.getLoadedManifestURLs().length) {
|
||||
this._preloadKeyboard();
|
||||
}
|
||||
}).bind(this);
|
||||
},
|
||||
|
||||
_updateLayouts: function km_updateLayouts(layouts) {
|
||||
var enabledApps = this.inputLayouts.processLayouts(layouts);
|
||||
|
||||
var manifestURLsToRemove =
|
||||
inputWindowManager.getLoadedManifestURLs().filter(
|
||||
manifestURL => !enabledApps.has(manifestURL)
|
||||
);
|
||||
|
||||
// explicitly assign a variable for code clarity
|
||||
var currentLayoutRemoved =
|
||||
inputWindowManager._onInputLayoutsRemoved(manifestURLsToRemove);
|
||||
|
||||
if (currentLayoutRemoved) {
|
||||
this._showingInputGroup = null;
|
||||
}
|
||||
|
||||
this._tryLaunchOnBoot();
|
||||
},
|
||||
|
||||
// a showing keyboard instance was OOM-killed. we need to relaunch something.
|
||||
_onKeyboardKilled: function km_onKeyboardKilled(manifestURL) {
|
||||
this._setKeyboardToShow(this._showingInputGroup);
|
||||
},
|
||||
|
||||
_onKeyboardReady: function km_onKeyboardReady() {
|
||||
this._showIMESwitcher();
|
||||
},
|
||||
|
||||
// As user focuses some input, see if we have a specified (in inputLayouts)
|
||||
// layout to launch for that input type group; if not, consult settings first.
|
||||
_activateKeyboard: function km_activateKeyboard(group) {
|
||||
// if we already have layouts for the group, no need to check default
|
||||
if (!this.inputLayouts.layouts[group]) {
|
||||
KeyboardHelper.checkDefaults(function changedDefaults() {
|
||||
KeyboardHelper.getLayouts({ enabled: true },
|
||||
this._updateLayouts.bind(this));
|
||||
KeyboardHelper.saveToSettings();
|
||||
}.bind(this));
|
||||
}
|
||||
// if there are still no keyboards to use, use text
|
||||
if (!this.inputLayouts.layouts[group]) {
|
||||
group = 'text';
|
||||
}
|
||||
|
||||
if (this.inputLayouts.layouts[group]._activeLayoutIdx !== undefined) {
|
||||
this._setKeyboardToShow(group);
|
||||
} else {
|
||||
this.inputLayouts.getGroupCurrentActiveLayoutIndexAsync(group)
|
||||
.then(currentActiveLayoutIdx => {
|
||||
this._setKeyboardToShow(group, currentActiveLayoutIdx);
|
||||
})
|
||||
.catch(e => {
|
||||
console.error(`KeyboardManager: failed to retrieve
|
||||
currentActiveLayoutIdx`, e);
|
||||
// launch keyboard anyway, just don't assign a default layout
|
||||
this._setKeyboardToShow(group);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_handleInputContextFocus: function km_handleInputContextFocus(evt) {
|
||||
var inputType = evt.detail.inputType;
|
||||
|
||||
if (VALUE_SELECTOR_INPUT_TYPES.indexOf(inputType) !== -1) {
|
||||
// Hide the input window (if there is).
|
||||
inputWindowManager.hideInputWindow();
|
||||
|
||||
// boardcast in the System app scope for the value selector controlling
|
||||
// logic to pick up and show the UI for this input.
|
||||
window.dispatchEvent(new CustomEvent('inputfocus', {
|
||||
detail: evt.detail
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
// display the keyboard for that group decided by input type
|
||||
// fallback to text for default if no group is found
|
||||
var group = TYPE_GROUP_MAPPING[inputType];
|
||||
this._debug('get focus event ' + inputType);
|
||||
this._activateKeyboard(group);
|
||||
},
|
||||
|
||||
handleEvent: function km_handleEvent(evt) {
|
||||
switch (evt.type) {
|
||||
case 'showallrequest':
|
||||
this._showImeMenu();
|
||||
evt.preventDefault();
|
||||
break;
|
||||
|
||||
case 'nextrequest':
|
||||
this._switchToNext();
|
||||
evt.preventDefault();
|
||||
break;
|
||||
|
||||
case 'inputcontextfocus':
|
||||
this._handleInputContextFocus(evt);
|
||||
evt.preventDefault();
|
||||
break;
|
||||
|
||||
case 'inputcontextblur':
|
||||
evt.preventDefault();
|
||||
this._debug('get blur event');
|
||||
inputWindowManager.hideInputWindow();
|
||||
this.imeSwitcher.hide();
|
||||
window.dispatchEvent(new CustomEvent('inputblur'));
|
||||
break;
|
||||
|
||||
case 'keyboardhide':
|
||||
this._showingInputGroup = null;
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
_preloadKeyboard: function km_preloadKeyboard() {
|
||||
if (!this.inputLayouts.layouts.text) {
|
||||
console.warn('trying to preload \'text\' layout while it\'s unavailable');
|
||||
return;
|
||||
}
|
||||
|
||||
this._debug('preloading a keyboard');
|
||||
|
||||
inputWindowManager.preloadInputWindow(this.inputLayouts.layouts.text[0]);
|
||||
},
|
||||
|
||||
// the generic "show an input window" function to be called by
|
||||
// activateKeyboard, switchToNext, and IMESwitcher callbacks.
|
||||
// if an ayout index is specified, launch to that layout. otherwise,
|
||||
// use the layout stored in inputLayouts.
|
||||
_setKeyboardToShow: function km_setKeyboardToShow(group, index) {
|
||||
if (!this.inputLayouts.layouts[group]) {
|
||||
console.warn('trying to set a layout group to show that doesnt exist');
|
||||
return;
|
||||
}
|
||||
|
||||
if (undefined === index) {
|
||||
index = this.inputLayouts.layouts[group]._activeLayoutIdx || 0;
|
||||
}
|
||||
this._debug('set layout to display: group=' + group + ' index=' + index);
|
||||
var layout = this.inputLayouts.layouts[group][index];
|
||||
|
||||
inputWindowManager.showInputWindow(layout);
|
||||
|
||||
this.inputLayouts.saveGroupsCurrentActiveLayout(layout);
|
||||
|
||||
this._showingInputGroup = group;
|
||||
},
|
||||
|
||||
/**
|
||||
* A half-permanent notification should display after the keyboard got
|
||||
* activated, and only hides after the keyboard got deactivated.
|
||||
*/
|
||||
_showIMESwitcher: function km_showIMESwitcher() {
|
||||
var showedGroup = this._showingInputGroup;
|
||||
if (!this.inputLayouts.layouts[showedGroup]) {
|
||||
return;
|
||||
}
|
||||
|
||||
var showedIndex = this.inputLayouts.layouts[showedGroup]._activeLayoutIdx;
|
||||
|
||||
// Need to make the message in spec: "Built-in Keyboard: English".
|
||||
// These labels are already localized in InputLayouts.
|
||||
var current = this.inputLayouts.layouts[showedGroup][showedIndex];
|
||||
|
||||
this.imeSwitcher.show(current.appName, current.name, current.nameL10nId);
|
||||
},
|
||||
|
||||
/* A small helper function for maintaining timeouts */
|
||||
_waitForSwitchTimeout: function km_waitForSwitchTimeout(callback) {
|
||||
clearTimeout(this._switchChangeTimeout);
|
||||
|
||||
this._switchChangeTimeout = setTimeout(callback, SWITCH_CHANGE_DELAY);
|
||||
},
|
||||
|
||||
_switchToNext: function km_switchToNext() {
|
||||
var showedGroup = this._showingInputGroup;
|
||||
|
||||
this._waitForSwitchTimeout(function keyboardSwitchLayout() {
|
||||
if (!this.inputLayouts.layouts[showedGroup]) {
|
||||
showedGroup = 'text';
|
||||
}
|
||||
var showedIndex = this.inputLayouts.layouts[showedGroup]._activeLayoutIdx;
|
||||
var length = this.inputLayouts.layouts[showedGroup].length;
|
||||
var index = (showedIndex + 1) % length;
|
||||
|
||||
this._setKeyboardToShow(showedGroup, index);
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
/*
|
||||
* Callback for ImeMenu.
|
||||
* If selectedIndex is defined, then some item of imeMenu was selected;
|
||||
* if it's not, then it was canceled.
|
||||
* The showedGroup param is bind()'ed by _showImeMenu
|
||||
* (resulting in a partial func)
|
||||
*/
|
||||
_imeMenuCallback: function km_imeMenuCallback(showedGroup, selectedIndex) {
|
||||
if (typeof selectedIndex === 'number') {
|
||||
// success: show the new keyboard
|
||||
this._setKeyboardToShow(showedGroup, selectedIndex);
|
||||
|
||||
// Hide the tray to show the app directly after user selected a new kb.
|
||||
window.dispatchEvent(new CustomEvent('keyboardchanged'));
|
||||
} else {
|
||||
// cancel: mimic the success callback to show the current keyboard.
|
||||
this._setKeyboardToShow(showedGroup);
|
||||
|
||||
// Hide the tray to show the app directly after user canceled.
|
||||
window.dispatchEvent(new CustomEvent('keyboardchangecanceled'));
|
||||
}
|
||||
},
|
||||
|
||||
// Show the input method menu
|
||||
_showImeMenu: function km_showImeMenu() {
|
||||
var showedGroup = this._showingInputGroup;
|
||||
var activeLayoutIdx =
|
||||
this.inputLayouts.layouts[showedGroup]._activeLayoutIdx;
|
||||
|
||||
this._waitForSwitchTimeout(function listLayouts() {
|
||||
var items = this.inputLayouts.layouts[showedGroup].map(
|
||||
function(layout, index) {
|
||||
return {
|
||||
name: layout.name,
|
||||
nameL10nId: layout.nameL10nId,
|
||||
appName: layout.appName,
|
||||
value: index,
|
||||
selected: (index === activeLayoutIdx)
|
||||
};
|
||||
});
|
||||
|
||||
inputWindowManager.hideInputWindow();
|
||||
|
||||
LazyLoader.load(['js/ime_menu.js']).then(() => {
|
||||
var menu = new ImeMenu(items, 'choose-option',
|
||||
this._imeMenuCallback.bind(this, showedGroup),
|
||||
this._imeMenuCallback.bind(this, showedGroup));
|
||||
menu.start();
|
||||
}).catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
|
||||
}.bind(this));
|
||||
}
|
||||
};
|
||||
Loading…
Reference in a new issue