872db4f11e
Native doesn't ever actually care about the attributes of keys, so move all of it up into the managed layer and move all of the key names down so they're defined once. Change-Id: Ic8ded13ce050b2b98744735ff50d11e8d882d7d5
1156 lines
40 KiB
C++
1156 lines
40 KiB
C++
/*
|
|
* Copyright (C) 2008 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#define LOG_TAG "KeyCharacterMap"
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#if HAVE_ANDROID_OS
|
|
#include <binder/Parcel.h>
|
|
#endif
|
|
|
|
#include <android/keycodes.h>
|
|
#include <input/InputEventLabels.h>
|
|
#include <input/Keyboard.h>
|
|
#include <input/KeyCharacterMap.h>
|
|
|
|
#include <utils/Log.h>
|
|
#include <utils/Errors.h>
|
|
#include <utils/Tokenizer.h>
|
|
#include <utils/Timers.h>
|
|
|
|
// Enables debug output for the parser.
|
|
#define DEBUG_PARSER 0
|
|
|
|
// Enables debug output for parser performance.
|
|
#define DEBUG_PARSER_PERFORMANCE 0
|
|
|
|
// Enables debug output for mapping.
|
|
#define DEBUG_MAPPING 0
|
|
|
|
|
|
namespace android {
|
|
|
|
static const char* WHITESPACE = " \t\r";
|
|
static const char* WHITESPACE_OR_PROPERTY_DELIMITER = " \t\r,:";
|
|
|
|
struct Modifier {
|
|
const char* label;
|
|
int32_t metaState;
|
|
};
|
|
static const Modifier modifiers[] = {
|
|
{ "shift", AMETA_SHIFT_ON },
|
|
{ "lshift", AMETA_SHIFT_LEFT_ON },
|
|
{ "rshift", AMETA_SHIFT_RIGHT_ON },
|
|
{ "alt", AMETA_ALT_ON },
|
|
{ "lalt", AMETA_ALT_LEFT_ON },
|
|
{ "ralt", AMETA_ALT_RIGHT_ON },
|
|
{ "ctrl", AMETA_CTRL_ON },
|
|
{ "lctrl", AMETA_CTRL_LEFT_ON },
|
|
{ "rctrl", AMETA_CTRL_RIGHT_ON },
|
|
{ "meta", AMETA_META_ON },
|
|
{ "lmeta", AMETA_META_LEFT_ON },
|
|
{ "rmeta", AMETA_META_RIGHT_ON },
|
|
{ "sym", AMETA_SYM_ON },
|
|
{ "fn", AMETA_FUNCTION_ON },
|
|
{ "capslock", AMETA_CAPS_LOCK_ON },
|
|
{ "numlock", AMETA_NUM_LOCK_ON },
|
|
{ "scrolllock", AMETA_SCROLL_LOCK_ON },
|
|
};
|
|
|
|
#if DEBUG_MAPPING
|
|
static String8 toString(const char16_t* chars, size_t numChars) {
|
|
String8 result;
|
|
for (size_t i = 0; i < numChars; i++) {
|
|
result.appendFormat(i == 0 ? "%d" : ", %d", chars[i]);
|
|
}
|
|
return result;
|
|
}
|
|
#endif
|
|
|
|
|
|
// --- KeyCharacterMap ---
|
|
|
|
sp<KeyCharacterMap> KeyCharacterMap::sEmpty = new KeyCharacterMap();
|
|
|
|
KeyCharacterMap::KeyCharacterMap() :
|
|
mType(KEYBOARD_TYPE_UNKNOWN) {
|
|
}
|
|
|
|
KeyCharacterMap::KeyCharacterMap(const KeyCharacterMap& other) :
|
|
RefBase(), mType(other.mType), mKeysByScanCode(other.mKeysByScanCode),
|
|
mKeysByUsageCode(other.mKeysByUsageCode) {
|
|
for (size_t i = 0; i < other.mKeys.size(); i++) {
|
|
mKeys.add(other.mKeys.keyAt(i), new Key(*other.mKeys.valueAt(i)));
|
|
}
|
|
}
|
|
|
|
KeyCharacterMap::~KeyCharacterMap() {
|
|
for (size_t i = 0; i < mKeys.size(); i++) {
|
|
Key* key = mKeys.editValueAt(i);
|
|
delete key;
|
|
}
|
|
}
|
|
|
|
status_t KeyCharacterMap::load(const String8& filename,
|
|
Format format, sp<KeyCharacterMap>* outMap) {
|
|
outMap->clear();
|
|
|
|
Tokenizer* tokenizer;
|
|
status_t status = Tokenizer::open(filename, &tokenizer);
|
|
if (status) {
|
|
ALOGE("Error %d opening key character map file %s.", status, filename.string());
|
|
} else {
|
|
status = load(tokenizer, format, outMap);
|
|
delete tokenizer;
|
|
}
|
|
return status;
|
|
}
|
|
|
|
status_t KeyCharacterMap::loadContents(const String8& filename, const char* contents,
|
|
Format format, sp<KeyCharacterMap>* outMap) {
|
|
outMap->clear();
|
|
|
|
Tokenizer* tokenizer;
|
|
status_t status = Tokenizer::fromContents(filename, contents, &tokenizer);
|
|
if (status) {
|
|
ALOGE("Error %d opening key character map.", status);
|
|
} else {
|
|
status = load(tokenizer, format, outMap);
|
|
delete tokenizer;
|
|
}
|
|
return status;
|
|
}
|
|
|
|
status_t KeyCharacterMap::load(Tokenizer* tokenizer,
|
|
Format format, sp<KeyCharacterMap>* outMap) {
|
|
status_t status = OK;
|
|
sp<KeyCharacterMap> map = new KeyCharacterMap();
|
|
if (!map.get()) {
|
|
ALOGE("Error allocating key character map.");
|
|
status = NO_MEMORY;
|
|
} else {
|
|
#if DEBUG_PARSER_PERFORMANCE
|
|
nsecs_t startTime = systemTime(SYSTEM_TIME_MONOTONIC);
|
|
#endif
|
|
Parser parser(map.get(), tokenizer, format);
|
|
status = parser.parse();
|
|
#if DEBUG_PARSER_PERFORMANCE
|
|
nsecs_t elapsedTime = systemTime(SYSTEM_TIME_MONOTONIC) - startTime;
|
|
ALOGD("Parsed key character map file '%s' %d lines in %0.3fms.",
|
|
tokenizer->getFilename().string(), tokenizer->getLineNumber(),
|
|
elapsedTime / 1000000.0);
|
|
#endif
|
|
if (!status) {
|
|
*outMap = map;
|
|
}
|
|
}
|
|
return status;
|
|
}
|
|
|
|
sp<KeyCharacterMap> KeyCharacterMap::combine(const sp<KeyCharacterMap>& base,
|
|
const sp<KeyCharacterMap>& overlay) {
|
|
if (overlay == NULL) {
|
|
return base;
|
|
}
|
|
if (base == NULL) {
|
|
return overlay;
|
|
}
|
|
|
|
sp<KeyCharacterMap> map = new KeyCharacterMap(*base.get());
|
|
for (size_t i = 0; i < overlay->mKeys.size(); i++) {
|
|
int32_t keyCode = overlay->mKeys.keyAt(i);
|
|
Key* key = overlay->mKeys.valueAt(i);
|
|
ssize_t oldIndex = map->mKeys.indexOfKey(keyCode);
|
|
if (oldIndex >= 0) {
|
|
delete map->mKeys.valueAt(oldIndex);
|
|
map->mKeys.editValueAt(oldIndex) = new Key(*key);
|
|
} else {
|
|
map->mKeys.add(keyCode, new Key(*key));
|
|
}
|
|
}
|
|
|
|
for (size_t i = 0; i < overlay->mKeysByScanCode.size(); i++) {
|
|
map->mKeysByScanCode.replaceValueFor(overlay->mKeysByScanCode.keyAt(i),
|
|
overlay->mKeysByScanCode.valueAt(i));
|
|
}
|
|
|
|
for (size_t i = 0; i < overlay->mKeysByUsageCode.size(); i++) {
|
|
map->mKeysByUsageCode.replaceValueFor(overlay->mKeysByUsageCode.keyAt(i),
|
|
overlay->mKeysByUsageCode.valueAt(i));
|
|
}
|
|
return map;
|
|
}
|
|
|
|
sp<KeyCharacterMap> KeyCharacterMap::empty() {
|
|
return sEmpty;
|
|
}
|
|
|
|
int32_t KeyCharacterMap::getKeyboardType() const {
|
|
return mType;
|
|
}
|
|
|
|
char16_t KeyCharacterMap::getDisplayLabel(int32_t keyCode) const {
|
|
char16_t result = 0;
|
|
const Key* key;
|
|
if (getKey(keyCode, &key)) {
|
|
result = key->label;
|
|
}
|
|
#if DEBUG_MAPPING
|
|
ALOGD("getDisplayLabel: keyCode=%d ~ Result %d.", keyCode, result);
|
|
#endif
|
|
return result;
|
|
}
|
|
|
|
char16_t KeyCharacterMap::getNumber(int32_t keyCode) const {
|
|
char16_t result = 0;
|
|
const Key* key;
|
|
if (getKey(keyCode, &key)) {
|
|
result = key->number;
|
|
}
|
|
#if DEBUG_MAPPING
|
|
ALOGD("getNumber: keyCode=%d ~ Result %d.", keyCode, result);
|
|
#endif
|
|
return result;
|
|
}
|
|
|
|
char16_t KeyCharacterMap::getCharacter(int32_t keyCode, int32_t metaState) const {
|
|
char16_t result = 0;
|
|
const Key* key;
|
|
const Behavior* behavior;
|
|
if (getKeyBehavior(keyCode, metaState, &key, &behavior)) {
|
|
result = behavior->character;
|
|
}
|
|
#if DEBUG_MAPPING
|
|
ALOGD("getCharacter: keyCode=%d, metaState=0x%08x ~ Result %d.", keyCode, metaState, result);
|
|
#endif
|
|
return result;
|
|
}
|
|
|
|
bool KeyCharacterMap::getFallbackAction(int32_t keyCode, int32_t metaState,
|
|
FallbackAction* outFallbackAction) const {
|
|
outFallbackAction->keyCode = 0;
|
|
outFallbackAction->metaState = 0;
|
|
|
|
bool result = false;
|
|
const Key* key;
|
|
const Behavior* behavior;
|
|
if (getKeyBehavior(keyCode, metaState, &key, &behavior)) {
|
|
if (behavior->fallbackKeyCode) {
|
|
outFallbackAction->keyCode = behavior->fallbackKeyCode;
|
|
outFallbackAction->metaState = metaState & ~behavior->metaState;
|
|
result = true;
|
|
}
|
|
}
|
|
#if DEBUG_MAPPING
|
|
ALOGD("getFallbackKeyCode: keyCode=%d, metaState=0x%08x ~ Result %s, "
|
|
"fallback keyCode=%d, fallback metaState=0x%08x.",
|
|
keyCode, metaState, result ? "true" : "false",
|
|
outFallbackAction->keyCode, outFallbackAction->metaState);
|
|
#endif
|
|
return result;
|
|
}
|
|
|
|
char16_t KeyCharacterMap::getMatch(int32_t keyCode, const char16_t* chars, size_t numChars,
|
|
int32_t metaState) const {
|
|
char16_t result = 0;
|
|
const Key* key;
|
|
if (getKey(keyCode, &key)) {
|
|
// Try to find the most general behavior that maps to this character.
|
|
// For example, the base key behavior will usually be last in the list.
|
|
// However, if we find a perfect meta state match for one behavior then use that one.
|
|
for (const Behavior* behavior = key->firstBehavior; behavior; behavior = behavior->next) {
|
|
if (behavior->character) {
|
|
for (size_t i = 0; i < numChars; i++) {
|
|
if (behavior->character == chars[i]) {
|
|
result = behavior->character;
|
|
if ((behavior->metaState & metaState) == behavior->metaState) {
|
|
goto ExactMatch;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
ExactMatch: ;
|
|
}
|
|
#if DEBUG_MAPPING
|
|
ALOGD("getMatch: keyCode=%d, chars=[%s], metaState=0x%08x ~ Result %d.",
|
|
keyCode, toString(chars, numChars).string(), metaState, result);
|
|
#endif
|
|
return result;
|
|
}
|
|
|
|
bool KeyCharacterMap::getEvents(int32_t deviceId, const char16_t* chars, size_t numChars,
|
|
Vector<KeyEvent>& outEvents) const {
|
|
nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
|
|
|
|
for (size_t i = 0; i < numChars; i++) {
|
|
int32_t keyCode, metaState;
|
|
char16_t ch = chars[i];
|
|
if (!findKey(ch, &keyCode, &metaState)) {
|
|
#if DEBUG_MAPPING
|
|
ALOGD("getEvents: deviceId=%d, chars=[%s] ~ Failed to find mapping for character %d.",
|
|
deviceId, toString(chars, numChars).string(), ch);
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
int32_t currentMetaState = 0;
|
|
addMetaKeys(outEvents, deviceId, metaState, true, now, ¤tMetaState);
|
|
addKey(outEvents, deviceId, keyCode, currentMetaState, true, now);
|
|
addKey(outEvents, deviceId, keyCode, currentMetaState, false, now);
|
|
addMetaKeys(outEvents, deviceId, metaState, false, now, ¤tMetaState);
|
|
}
|
|
#if DEBUG_MAPPING
|
|
ALOGD("getEvents: deviceId=%d, chars=[%s] ~ Generated %d events.",
|
|
deviceId, toString(chars, numChars).string(), int32_t(outEvents.size()));
|
|
for (size_t i = 0; i < outEvents.size(); i++) {
|
|
ALOGD(" Key: keyCode=%d, metaState=0x%08x, %s.",
|
|
outEvents[i].getKeyCode(), outEvents[i].getMetaState(),
|
|
outEvents[i].getAction() == AKEY_EVENT_ACTION_DOWN ? "down" : "up");
|
|
}
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
status_t KeyCharacterMap::mapKey(int32_t scanCode, int32_t usageCode, int32_t* outKeyCode) const {
|
|
if (usageCode) {
|
|
ssize_t index = mKeysByUsageCode.indexOfKey(usageCode);
|
|
if (index >= 0) {
|
|
#if DEBUG_MAPPING
|
|
ALOGD("mapKey: scanCode=%d, usageCode=0x%08x ~ Result keyCode=%d.",
|
|
scanCode, usageCode, *outKeyCode);
|
|
#endif
|
|
*outKeyCode = mKeysByUsageCode.valueAt(index);
|
|
return OK;
|
|
}
|
|
}
|
|
if (scanCode) {
|
|
ssize_t index = mKeysByScanCode.indexOfKey(scanCode);
|
|
if (index >= 0) {
|
|
#if DEBUG_MAPPING
|
|
ALOGD("mapKey: scanCode=%d, usageCode=0x%08x ~ Result keyCode=%d.",
|
|
scanCode, usageCode, *outKeyCode);
|
|
#endif
|
|
*outKeyCode = mKeysByScanCode.valueAt(index);
|
|
return OK;
|
|
}
|
|
}
|
|
|
|
#if DEBUG_MAPPING
|
|
ALOGD("mapKey: scanCode=%d, usageCode=0x%08x ~ Failed.", scanCode, usageCode);
|
|
#endif
|
|
*outKeyCode = AKEYCODE_UNKNOWN;
|
|
return NAME_NOT_FOUND;
|
|
}
|
|
|
|
bool KeyCharacterMap::getKey(int32_t keyCode, const Key** outKey) const {
|
|
ssize_t index = mKeys.indexOfKey(keyCode);
|
|
if (index >= 0) {
|
|
*outKey = mKeys.valueAt(index);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool KeyCharacterMap::getKeyBehavior(int32_t keyCode, int32_t metaState,
|
|
const Key** outKey, const Behavior** outBehavior) const {
|
|
const Key* key;
|
|
if (getKey(keyCode, &key)) {
|
|
const Behavior* behavior = key->firstBehavior;
|
|
while (behavior) {
|
|
if (matchesMetaState(metaState, behavior->metaState)) {
|
|
*outKey = key;
|
|
*outBehavior = behavior;
|
|
return true;
|
|
}
|
|
behavior = behavior->next;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool KeyCharacterMap::matchesMetaState(int32_t eventMetaState, int32_t behaviorMetaState) {
|
|
// Behavior must have at least the set of meta states specified.
|
|
// And if the key event has CTRL, ALT or META then the behavior must exactly
|
|
// match those, taking into account that a behavior can specify that it handles
|
|
// one, both or either of a left/right modifier pair.
|
|
if ((eventMetaState & behaviorMetaState) == behaviorMetaState) {
|
|
const int32_t EXACT_META_STATES =
|
|
AMETA_CTRL_ON | AMETA_CTRL_LEFT_ON | AMETA_CTRL_RIGHT_ON
|
|
| AMETA_ALT_ON | AMETA_ALT_LEFT_ON | AMETA_ALT_RIGHT_ON
|
|
| AMETA_META_ON | AMETA_META_LEFT_ON | AMETA_META_RIGHT_ON;
|
|
int32_t unmatchedMetaState = eventMetaState & ~behaviorMetaState & EXACT_META_STATES;
|
|
if (behaviorMetaState & AMETA_CTRL_ON) {
|
|
unmatchedMetaState &= ~(AMETA_CTRL_LEFT_ON | AMETA_CTRL_RIGHT_ON);
|
|
} else if (behaviorMetaState & (AMETA_CTRL_LEFT_ON | AMETA_CTRL_RIGHT_ON)) {
|
|
unmatchedMetaState &= ~AMETA_CTRL_ON;
|
|
}
|
|
if (behaviorMetaState & AMETA_ALT_ON) {
|
|
unmatchedMetaState &= ~(AMETA_ALT_LEFT_ON | AMETA_ALT_RIGHT_ON);
|
|
} else if (behaviorMetaState & (AMETA_ALT_LEFT_ON | AMETA_ALT_RIGHT_ON)) {
|
|
unmatchedMetaState &= ~AMETA_ALT_ON;
|
|
}
|
|
if (behaviorMetaState & AMETA_META_ON) {
|
|
unmatchedMetaState &= ~(AMETA_META_LEFT_ON | AMETA_META_RIGHT_ON);
|
|
} else if (behaviorMetaState & (AMETA_META_LEFT_ON | AMETA_META_RIGHT_ON)) {
|
|
unmatchedMetaState &= ~AMETA_META_ON;
|
|
}
|
|
return !unmatchedMetaState;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool KeyCharacterMap::findKey(char16_t ch, int32_t* outKeyCode, int32_t* outMetaState) const {
|
|
if (!ch) {
|
|
return false;
|
|
}
|
|
|
|
for (size_t i = 0; i < mKeys.size(); i++) {
|
|
const Key* key = mKeys.valueAt(i);
|
|
|
|
// Try to find the most general behavior that maps to this character.
|
|
// For example, the base key behavior will usually be last in the list.
|
|
const Behavior* found = NULL;
|
|
for (const Behavior* behavior = key->firstBehavior; behavior; behavior = behavior->next) {
|
|
if (behavior->character == ch) {
|
|
found = behavior;
|
|
}
|
|
}
|
|
if (found) {
|
|
*outKeyCode = mKeys.keyAt(i);
|
|
*outMetaState = found->metaState;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void KeyCharacterMap::addKey(Vector<KeyEvent>& outEvents,
|
|
int32_t deviceId, int32_t keyCode, int32_t metaState, bool down, nsecs_t time) {
|
|
outEvents.push();
|
|
KeyEvent& event = outEvents.editTop();
|
|
event.initialize(deviceId, AINPUT_SOURCE_KEYBOARD,
|
|
down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP,
|
|
0, keyCode, 0, metaState, 0, time, time);
|
|
}
|
|
|
|
void KeyCharacterMap::addMetaKeys(Vector<KeyEvent>& outEvents,
|
|
int32_t deviceId, int32_t metaState, bool down, nsecs_t time,
|
|
int32_t* currentMetaState) {
|
|
// Add and remove meta keys symmetrically.
|
|
if (down) {
|
|
addLockedMetaKey(outEvents, deviceId, metaState, time,
|
|
AKEYCODE_CAPS_LOCK, AMETA_CAPS_LOCK_ON, currentMetaState);
|
|
addLockedMetaKey(outEvents, deviceId, metaState, time,
|
|
AKEYCODE_NUM_LOCK, AMETA_NUM_LOCK_ON, currentMetaState);
|
|
addLockedMetaKey(outEvents, deviceId, metaState, time,
|
|
AKEYCODE_SCROLL_LOCK, AMETA_SCROLL_LOCK_ON, currentMetaState);
|
|
|
|
addDoubleEphemeralMetaKey(outEvents, deviceId, metaState, true, time,
|
|
AKEYCODE_SHIFT_LEFT, AMETA_SHIFT_LEFT_ON,
|
|
AKEYCODE_SHIFT_RIGHT, AMETA_SHIFT_RIGHT_ON,
|
|
AMETA_SHIFT_ON, currentMetaState);
|
|
addDoubleEphemeralMetaKey(outEvents, deviceId, metaState, true, time,
|
|
AKEYCODE_ALT_LEFT, AMETA_ALT_LEFT_ON,
|
|
AKEYCODE_ALT_RIGHT, AMETA_ALT_RIGHT_ON,
|
|
AMETA_ALT_ON, currentMetaState);
|
|
addDoubleEphemeralMetaKey(outEvents, deviceId, metaState, true, time,
|
|
AKEYCODE_CTRL_LEFT, AMETA_CTRL_LEFT_ON,
|
|
AKEYCODE_CTRL_RIGHT, AMETA_CTRL_RIGHT_ON,
|
|
AMETA_CTRL_ON, currentMetaState);
|
|
addDoubleEphemeralMetaKey(outEvents, deviceId, metaState, true, time,
|
|
AKEYCODE_META_LEFT, AMETA_META_LEFT_ON,
|
|
AKEYCODE_META_RIGHT, AMETA_META_RIGHT_ON,
|
|
AMETA_META_ON, currentMetaState);
|
|
|
|
addSingleEphemeralMetaKey(outEvents, deviceId, metaState, true, time,
|
|
AKEYCODE_SYM, AMETA_SYM_ON, currentMetaState);
|
|
addSingleEphemeralMetaKey(outEvents, deviceId, metaState, true, time,
|
|
AKEYCODE_FUNCTION, AMETA_FUNCTION_ON, currentMetaState);
|
|
} else {
|
|
addSingleEphemeralMetaKey(outEvents, deviceId, metaState, false, time,
|
|
AKEYCODE_FUNCTION, AMETA_FUNCTION_ON, currentMetaState);
|
|
addSingleEphemeralMetaKey(outEvents, deviceId, metaState, false, time,
|
|
AKEYCODE_SYM, AMETA_SYM_ON, currentMetaState);
|
|
|
|
addDoubleEphemeralMetaKey(outEvents, deviceId, metaState, false, time,
|
|
AKEYCODE_META_LEFT, AMETA_META_LEFT_ON,
|
|
AKEYCODE_META_RIGHT, AMETA_META_RIGHT_ON,
|
|
AMETA_META_ON, currentMetaState);
|
|
addDoubleEphemeralMetaKey(outEvents, deviceId, metaState, false, time,
|
|
AKEYCODE_CTRL_LEFT, AMETA_CTRL_LEFT_ON,
|
|
AKEYCODE_CTRL_RIGHT, AMETA_CTRL_RIGHT_ON,
|
|
AMETA_CTRL_ON, currentMetaState);
|
|
addDoubleEphemeralMetaKey(outEvents, deviceId, metaState, false, time,
|
|
AKEYCODE_ALT_LEFT, AMETA_ALT_LEFT_ON,
|
|
AKEYCODE_ALT_RIGHT, AMETA_ALT_RIGHT_ON,
|
|
AMETA_ALT_ON, currentMetaState);
|
|
addDoubleEphemeralMetaKey(outEvents, deviceId, metaState, false, time,
|
|
AKEYCODE_SHIFT_LEFT, AMETA_SHIFT_LEFT_ON,
|
|
AKEYCODE_SHIFT_RIGHT, AMETA_SHIFT_RIGHT_ON,
|
|
AMETA_SHIFT_ON, currentMetaState);
|
|
|
|
addLockedMetaKey(outEvents, deviceId, metaState, time,
|
|
AKEYCODE_SCROLL_LOCK, AMETA_SCROLL_LOCK_ON, currentMetaState);
|
|
addLockedMetaKey(outEvents, deviceId, metaState, time,
|
|
AKEYCODE_NUM_LOCK, AMETA_NUM_LOCK_ON, currentMetaState);
|
|
addLockedMetaKey(outEvents, deviceId, metaState, time,
|
|
AKEYCODE_CAPS_LOCK, AMETA_CAPS_LOCK_ON, currentMetaState);
|
|
}
|
|
}
|
|
|
|
bool KeyCharacterMap::addSingleEphemeralMetaKey(Vector<KeyEvent>& outEvents,
|
|
int32_t deviceId, int32_t metaState, bool down, nsecs_t time,
|
|
int32_t keyCode, int32_t keyMetaState,
|
|
int32_t* currentMetaState) {
|
|
if ((metaState & keyMetaState) == keyMetaState) {
|
|
*currentMetaState = updateMetaState(keyCode, down, *currentMetaState);
|
|
addKey(outEvents, deviceId, keyCode, *currentMetaState, down, time);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void KeyCharacterMap::addDoubleEphemeralMetaKey(Vector<KeyEvent>& outEvents,
|
|
int32_t deviceId, int32_t metaState, bool down, nsecs_t time,
|
|
int32_t leftKeyCode, int32_t leftKeyMetaState,
|
|
int32_t rightKeyCode, int32_t rightKeyMetaState,
|
|
int32_t eitherKeyMetaState,
|
|
int32_t* currentMetaState) {
|
|
bool specific = false;
|
|
specific |= addSingleEphemeralMetaKey(outEvents, deviceId, metaState, down, time,
|
|
leftKeyCode, leftKeyMetaState, currentMetaState);
|
|
specific |= addSingleEphemeralMetaKey(outEvents, deviceId, metaState, down, time,
|
|
rightKeyCode, rightKeyMetaState, currentMetaState);
|
|
|
|
if (!specific) {
|
|
addSingleEphemeralMetaKey(outEvents, deviceId, metaState, down, time,
|
|
leftKeyCode, eitherKeyMetaState, currentMetaState);
|
|
}
|
|
}
|
|
|
|
void KeyCharacterMap::addLockedMetaKey(Vector<KeyEvent>& outEvents,
|
|
int32_t deviceId, int32_t metaState, nsecs_t time,
|
|
int32_t keyCode, int32_t keyMetaState,
|
|
int32_t* currentMetaState) {
|
|
if ((metaState & keyMetaState) == keyMetaState) {
|
|
*currentMetaState = updateMetaState(keyCode, true, *currentMetaState);
|
|
addKey(outEvents, deviceId, keyCode, *currentMetaState, true, time);
|
|
*currentMetaState = updateMetaState(keyCode, false, *currentMetaState);
|
|
addKey(outEvents, deviceId, keyCode, *currentMetaState, false, time);
|
|
}
|
|
}
|
|
|
|
#if HAVE_ANDROID_OS
|
|
sp<KeyCharacterMap> KeyCharacterMap::readFromParcel(Parcel* parcel) {
|
|
sp<KeyCharacterMap> map = new KeyCharacterMap();
|
|
map->mType = parcel->readInt32();
|
|
size_t numKeys = parcel->readInt32();
|
|
if (parcel->errorCheck()) {
|
|
return NULL;
|
|
}
|
|
|
|
for (size_t i = 0; i < numKeys; i++) {
|
|
int32_t keyCode = parcel->readInt32();
|
|
char16_t label = parcel->readInt32();
|
|
char16_t number = parcel->readInt32();
|
|
if (parcel->errorCheck()) {
|
|
return NULL;
|
|
}
|
|
|
|
Key* key = new Key();
|
|
key->label = label;
|
|
key->number = number;
|
|
map->mKeys.add(keyCode, key);
|
|
|
|
Behavior* lastBehavior = NULL;
|
|
while (parcel->readInt32()) {
|
|
int32_t metaState = parcel->readInt32();
|
|
char16_t character = parcel->readInt32();
|
|
int32_t fallbackKeyCode = parcel->readInt32();
|
|
if (parcel->errorCheck()) {
|
|
return NULL;
|
|
}
|
|
|
|
Behavior* behavior = new Behavior();
|
|
behavior->metaState = metaState;
|
|
behavior->character = character;
|
|
behavior->fallbackKeyCode = fallbackKeyCode;
|
|
if (lastBehavior) {
|
|
lastBehavior->next = behavior;
|
|
} else {
|
|
key->firstBehavior = behavior;
|
|
}
|
|
lastBehavior = behavior;
|
|
}
|
|
|
|
if (parcel->errorCheck()) {
|
|
return NULL;
|
|
}
|
|
}
|
|
return map;
|
|
}
|
|
|
|
void KeyCharacterMap::writeToParcel(Parcel* parcel) const {
|
|
parcel->writeInt32(mType);
|
|
|
|
size_t numKeys = mKeys.size();
|
|
parcel->writeInt32(numKeys);
|
|
for (size_t i = 0; i < numKeys; i++) {
|
|
int32_t keyCode = mKeys.keyAt(i);
|
|
const Key* key = mKeys.valueAt(i);
|
|
parcel->writeInt32(keyCode);
|
|
parcel->writeInt32(key->label);
|
|
parcel->writeInt32(key->number);
|
|
for (const Behavior* behavior = key->firstBehavior; behavior != NULL;
|
|
behavior = behavior->next) {
|
|
parcel->writeInt32(1);
|
|
parcel->writeInt32(behavior->metaState);
|
|
parcel->writeInt32(behavior->character);
|
|
parcel->writeInt32(behavior->fallbackKeyCode);
|
|
}
|
|
parcel->writeInt32(0);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
// --- KeyCharacterMap::Key ---
|
|
|
|
KeyCharacterMap::Key::Key() :
|
|
label(0), number(0), firstBehavior(NULL) {
|
|
}
|
|
|
|
KeyCharacterMap::Key::Key(const Key& other) :
|
|
label(other.label), number(other.number),
|
|
firstBehavior(other.firstBehavior ? new Behavior(*other.firstBehavior) : NULL) {
|
|
}
|
|
|
|
KeyCharacterMap::Key::~Key() {
|
|
Behavior* behavior = firstBehavior;
|
|
while (behavior) {
|
|
Behavior* next = behavior->next;
|
|
delete behavior;
|
|
behavior = next;
|
|
}
|
|
}
|
|
|
|
|
|
// --- KeyCharacterMap::Behavior ---
|
|
|
|
KeyCharacterMap::Behavior::Behavior() :
|
|
next(NULL), metaState(0), character(0), fallbackKeyCode(0) {
|
|
}
|
|
|
|
KeyCharacterMap::Behavior::Behavior(const Behavior& other) :
|
|
next(other.next ? new Behavior(*other.next) : NULL),
|
|
metaState(other.metaState), character(other.character),
|
|
fallbackKeyCode(other.fallbackKeyCode) {
|
|
}
|
|
|
|
|
|
// --- KeyCharacterMap::Parser ---
|
|
|
|
KeyCharacterMap::Parser::Parser(KeyCharacterMap* map, Tokenizer* tokenizer, Format format) :
|
|
mMap(map), mTokenizer(tokenizer), mFormat(format), mState(STATE_TOP) {
|
|
}
|
|
|
|
KeyCharacterMap::Parser::~Parser() {
|
|
}
|
|
|
|
status_t KeyCharacterMap::Parser::parse() {
|
|
while (!mTokenizer->isEof()) {
|
|
#if DEBUG_PARSER
|
|
ALOGD("Parsing %s: '%s'.", mTokenizer->getLocation().string(),
|
|
mTokenizer->peekRemainderOfLine().string());
|
|
#endif
|
|
|
|
mTokenizer->skipDelimiters(WHITESPACE);
|
|
|
|
if (!mTokenizer->isEol() && mTokenizer->peekChar() != '#') {
|
|
switch (mState) {
|
|
case STATE_TOP: {
|
|
String8 keywordToken = mTokenizer->nextToken(WHITESPACE);
|
|
if (keywordToken == "type") {
|
|
mTokenizer->skipDelimiters(WHITESPACE);
|
|
status_t status = parseType();
|
|
if (status) return status;
|
|
} else if (keywordToken == "map") {
|
|
mTokenizer->skipDelimiters(WHITESPACE);
|
|
status_t status = parseMap();
|
|
if (status) return status;
|
|
} else if (keywordToken == "key") {
|
|
mTokenizer->skipDelimiters(WHITESPACE);
|
|
status_t status = parseKey();
|
|
if (status) return status;
|
|
} else {
|
|
ALOGE("%s: Expected keyword, got '%s'.", mTokenizer->getLocation().string(),
|
|
keywordToken.string());
|
|
return BAD_VALUE;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case STATE_KEY: {
|
|
status_t status = parseKeyProperty();
|
|
if (status) return status;
|
|
break;
|
|
}
|
|
}
|
|
|
|
mTokenizer->skipDelimiters(WHITESPACE);
|
|
if (!mTokenizer->isEol() && mTokenizer->peekChar() != '#') {
|
|
ALOGE("%s: Expected end of line or trailing comment, got '%s'.",
|
|
mTokenizer->getLocation().string(),
|
|
mTokenizer->peekRemainderOfLine().string());
|
|
return BAD_VALUE;
|
|
}
|
|
}
|
|
|
|
mTokenizer->nextLine();
|
|
}
|
|
|
|
if (mState != STATE_TOP) {
|
|
ALOGE("%s: Unterminated key description at end of file.",
|
|
mTokenizer->getLocation().string());
|
|
return BAD_VALUE;
|
|
}
|
|
|
|
if (mMap->mType == KEYBOARD_TYPE_UNKNOWN) {
|
|
ALOGE("%s: Keyboard layout missing required keyboard 'type' declaration.",
|
|
mTokenizer->getLocation().string());
|
|
return BAD_VALUE;
|
|
}
|
|
|
|
if (mFormat == FORMAT_BASE) {
|
|
if (mMap->mType == KEYBOARD_TYPE_OVERLAY) {
|
|
ALOGE("%s: Base keyboard layout must specify a keyboard 'type' other than 'OVERLAY'.",
|
|
mTokenizer->getLocation().string());
|
|
return BAD_VALUE;
|
|
}
|
|
} else if (mFormat == FORMAT_OVERLAY) {
|
|
if (mMap->mType != KEYBOARD_TYPE_OVERLAY) {
|
|
ALOGE("%s: Overlay keyboard layout missing required keyboard "
|
|
"'type OVERLAY' declaration.",
|
|
mTokenizer->getLocation().string());
|
|
return BAD_VALUE;
|
|
}
|
|
}
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
status_t KeyCharacterMap::Parser::parseType() {
|
|
if (mMap->mType != KEYBOARD_TYPE_UNKNOWN) {
|
|
ALOGE("%s: Duplicate keyboard 'type' declaration.",
|
|
mTokenizer->getLocation().string());
|
|
return BAD_VALUE;
|
|
}
|
|
|
|
KeyboardType type;
|
|
String8 typeToken = mTokenizer->nextToken(WHITESPACE);
|
|
if (typeToken == "NUMERIC") {
|
|
type = KEYBOARD_TYPE_NUMERIC;
|
|
} else if (typeToken == "PREDICTIVE") {
|
|
type = KEYBOARD_TYPE_PREDICTIVE;
|
|
} else if (typeToken == "ALPHA") {
|
|
type = KEYBOARD_TYPE_ALPHA;
|
|
} else if (typeToken == "FULL") {
|
|
type = KEYBOARD_TYPE_FULL;
|
|
} else if (typeToken == "SPECIAL_FUNCTION") {
|
|
type = KEYBOARD_TYPE_SPECIAL_FUNCTION;
|
|
} else if (typeToken == "OVERLAY") {
|
|
type = KEYBOARD_TYPE_OVERLAY;
|
|
} else {
|
|
ALOGE("%s: Expected keyboard type label, got '%s'.", mTokenizer->getLocation().string(),
|
|
typeToken.string());
|
|
return BAD_VALUE;
|
|
}
|
|
|
|
#if DEBUG_PARSER
|
|
ALOGD("Parsed type: type=%d.", type);
|
|
#endif
|
|
mMap->mType = type;
|
|
return NO_ERROR;
|
|
}
|
|
|
|
status_t KeyCharacterMap::Parser::parseMap() {
|
|
String8 keywordToken = mTokenizer->nextToken(WHITESPACE);
|
|
if (keywordToken == "key") {
|
|
mTokenizer->skipDelimiters(WHITESPACE);
|
|
return parseMapKey();
|
|
}
|
|
ALOGE("%s: Expected keyword after 'map', got '%s'.", mTokenizer->getLocation().string(),
|
|
keywordToken.string());
|
|
return BAD_VALUE;
|
|
}
|
|
|
|
status_t KeyCharacterMap::Parser::parseMapKey() {
|
|
String8 codeToken = mTokenizer->nextToken(WHITESPACE);
|
|
bool mapUsage = false;
|
|
if (codeToken == "usage") {
|
|
mapUsage = true;
|
|
mTokenizer->skipDelimiters(WHITESPACE);
|
|
codeToken = mTokenizer->nextToken(WHITESPACE);
|
|
}
|
|
|
|
char* end;
|
|
int32_t code = int32_t(strtol(codeToken.string(), &end, 0));
|
|
if (*end) {
|
|
ALOGE("%s: Expected key %s number, got '%s'.", mTokenizer->getLocation().string(),
|
|
mapUsage ? "usage" : "scan code", codeToken.string());
|
|
return BAD_VALUE;
|
|
}
|
|
KeyedVector<int32_t, int32_t>& map =
|
|
mapUsage ? mMap->mKeysByUsageCode : mMap->mKeysByScanCode;
|
|
if (map.indexOfKey(code) >= 0) {
|
|
ALOGE("%s: Duplicate entry for key %s '%s'.", mTokenizer->getLocation().string(),
|
|
mapUsage ? "usage" : "scan code", codeToken.string());
|
|
return BAD_VALUE;
|
|
}
|
|
|
|
mTokenizer->skipDelimiters(WHITESPACE);
|
|
String8 keyCodeToken = mTokenizer->nextToken(WHITESPACE);
|
|
int32_t keyCode = getKeyCodeByLabel(keyCodeToken.string());
|
|
if (!keyCode) {
|
|
ALOGE("%s: Expected key code label, got '%s'.", mTokenizer->getLocation().string(),
|
|
keyCodeToken.string());
|
|
return BAD_VALUE;
|
|
}
|
|
|
|
#if DEBUG_PARSER
|
|
ALOGD("Parsed map key %s: code=%d, keyCode=%d.",
|
|
mapUsage ? "usage" : "scan code", code, keyCode);
|
|
#endif
|
|
map.add(code, keyCode);
|
|
return NO_ERROR;
|
|
}
|
|
|
|
status_t KeyCharacterMap::Parser::parseKey() {
|
|
String8 keyCodeToken = mTokenizer->nextToken(WHITESPACE);
|
|
int32_t keyCode = getKeyCodeByLabel(keyCodeToken.string());
|
|
if (!keyCode) {
|
|
ALOGE("%s: Expected key code label, got '%s'.", mTokenizer->getLocation().string(),
|
|
keyCodeToken.string());
|
|
return BAD_VALUE;
|
|
}
|
|
if (mMap->mKeys.indexOfKey(keyCode) >= 0) {
|
|
ALOGE("%s: Duplicate entry for key code '%s'.", mTokenizer->getLocation().string(),
|
|
keyCodeToken.string());
|
|
return BAD_VALUE;
|
|
}
|
|
|
|
mTokenizer->skipDelimiters(WHITESPACE);
|
|
String8 openBraceToken = mTokenizer->nextToken(WHITESPACE);
|
|
if (openBraceToken != "{") {
|
|
ALOGE("%s: Expected '{' after key code label, got '%s'.",
|
|
mTokenizer->getLocation().string(), openBraceToken.string());
|
|
return BAD_VALUE;
|
|
}
|
|
|
|
#if DEBUG_PARSER
|
|
ALOGD("Parsed beginning of key: keyCode=%d.", keyCode);
|
|
#endif
|
|
mKeyCode = keyCode;
|
|
mMap->mKeys.add(keyCode, new Key());
|
|
mState = STATE_KEY;
|
|
return NO_ERROR;
|
|
}
|
|
|
|
status_t KeyCharacterMap::Parser::parseKeyProperty() {
|
|
Key* key = mMap->mKeys.valueFor(mKeyCode);
|
|
String8 token = mTokenizer->nextToken(WHITESPACE_OR_PROPERTY_DELIMITER);
|
|
if (token == "}") {
|
|
mState = STATE_TOP;
|
|
return finishKey(key);
|
|
}
|
|
|
|
Vector<Property> properties;
|
|
|
|
// Parse all comma-delimited property names up to the first colon.
|
|
for (;;) {
|
|
if (token == "label") {
|
|
properties.add(Property(PROPERTY_LABEL));
|
|
} else if (token == "number") {
|
|
properties.add(Property(PROPERTY_NUMBER));
|
|
} else {
|
|
int32_t metaState;
|
|
status_t status = parseModifier(token, &metaState);
|
|
if (status) {
|
|
ALOGE("%s: Expected a property name or modifier, got '%s'.",
|
|
mTokenizer->getLocation().string(), token.string());
|
|
return status;
|
|
}
|
|
properties.add(Property(PROPERTY_META, metaState));
|
|
}
|
|
|
|
mTokenizer->skipDelimiters(WHITESPACE);
|
|
if (!mTokenizer->isEol()) {
|
|
char ch = mTokenizer->nextChar();
|
|
if (ch == ':') {
|
|
break;
|
|
} else if (ch == ',') {
|
|
mTokenizer->skipDelimiters(WHITESPACE);
|
|
token = mTokenizer->nextToken(WHITESPACE_OR_PROPERTY_DELIMITER);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
ALOGE("%s: Expected ',' or ':' after property name.",
|
|
mTokenizer->getLocation().string());
|
|
return BAD_VALUE;
|
|
}
|
|
|
|
// Parse behavior after the colon.
|
|
mTokenizer->skipDelimiters(WHITESPACE);
|
|
|
|
Behavior behavior;
|
|
bool haveCharacter = false;
|
|
bool haveFallback = false;
|
|
|
|
do {
|
|
char ch = mTokenizer->peekChar();
|
|
if (ch == '\'') {
|
|
char16_t character;
|
|
status_t status = parseCharacterLiteral(&character);
|
|
if (status || !character) {
|
|
ALOGE("%s: Invalid character literal for key.",
|
|
mTokenizer->getLocation().string());
|
|
return BAD_VALUE;
|
|
}
|
|
if (haveCharacter) {
|
|
ALOGE("%s: Cannot combine multiple character literals or 'none'.",
|
|
mTokenizer->getLocation().string());
|
|
return BAD_VALUE;
|
|
}
|
|
behavior.character = character;
|
|
haveCharacter = true;
|
|
} else {
|
|
token = mTokenizer->nextToken(WHITESPACE);
|
|
if (token == "none") {
|
|
if (haveCharacter) {
|
|
ALOGE("%s: Cannot combine multiple character literals or 'none'.",
|
|
mTokenizer->getLocation().string());
|
|
return BAD_VALUE;
|
|
}
|
|
haveCharacter = true;
|
|
} else if (token == "fallback") {
|
|
mTokenizer->skipDelimiters(WHITESPACE);
|
|
token = mTokenizer->nextToken(WHITESPACE);
|
|
int32_t keyCode = getKeyCodeByLabel(token.string());
|
|
if (!keyCode) {
|
|
ALOGE("%s: Invalid key code label for fallback behavior, got '%s'.",
|
|
mTokenizer->getLocation().string(),
|
|
token.string());
|
|
return BAD_VALUE;
|
|
}
|
|
if (haveFallback) {
|
|
ALOGE("%s: Cannot combine multiple fallback key codes.",
|
|
mTokenizer->getLocation().string());
|
|
return BAD_VALUE;
|
|
}
|
|
behavior.fallbackKeyCode = keyCode;
|
|
haveFallback = true;
|
|
} else {
|
|
ALOGE("%s: Expected a key behavior after ':'.",
|
|
mTokenizer->getLocation().string());
|
|
return BAD_VALUE;
|
|
}
|
|
}
|
|
|
|
mTokenizer->skipDelimiters(WHITESPACE);
|
|
} while (!mTokenizer->isEol() && mTokenizer->peekChar() != '#');
|
|
|
|
// Add the behavior.
|
|
for (size_t i = 0; i < properties.size(); i++) {
|
|
const Property& property = properties.itemAt(i);
|
|
switch (property.property) {
|
|
case PROPERTY_LABEL:
|
|
if (key->label) {
|
|
ALOGE("%s: Duplicate label for key.",
|
|
mTokenizer->getLocation().string());
|
|
return BAD_VALUE;
|
|
}
|
|
key->label = behavior.character;
|
|
#if DEBUG_PARSER
|
|
ALOGD("Parsed key label: keyCode=%d, label=%d.", mKeyCode, key->label);
|
|
#endif
|
|
break;
|
|
case PROPERTY_NUMBER:
|
|
if (key->number) {
|
|
ALOGE("%s: Duplicate number for key.",
|
|
mTokenizer->getLocation().string());
|
|
return BAD_VALUE;
|
|
}
|
|
key->number = behavior.character;
|
|
#if DEBUG_PARSER
|
|
ALOGD("Parsed key number: keyCode=%d, number=%d.", mKeyCode, key->number);
|
|
#endif
|
|
break;
|
|
case PROPERTY_META: {
|
|
for (Behavior* b = key->firstBehavior; b; b = b->next) {
|
|
if (b->metaState == property.metaState) {
|
|
ALOGE("%s: Duplicate key behavior for modifier.",
|
|
mTokenizer->getLocation().string());
|
|
return BAD_VALUE;
|
|
}
|
|
}
|
|
Behavior* newBehavior = new Behavior(behavior);
|
|
newBehavior->metaState = property.metaState;
|
|
newBehavior->next = key->firstBehavior;
|
|
key->firstBehavior = newBehavior;
|
|
#if DEBUG_PARSER
|
|
ALOGD("Parsed key meta: keyCode=%d, meta=0x%x, char=%d, fallback=%d.", mKeyCode,
|
|
newBehavior->metaState, newBehavior->character, newBehavior->fallbackKeyCode);
|
|
#endif
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return NO_ERROR;
|
|
}
|
|
|
|
status_t KeyCharacterMap::Parser::finishKey(Key* key) {
|
|
// Fill in default number property.
|
|
if (!key->number) {
|
|
char16_t digit = 0;
|
|
char16_t symbol = 0;
|
|
for (Behavior* b = key->firstBehavior; b; b = b->next) {
|
|
char16_t ch = b->character;
|
|
if (ch) {
|
|
if (ch >= '0' && ch <= '9') {
|
|
digit = ch;
|
|
} else if (ch == '(' || ch == ')' || ch == '#' || ch == '*'
|
|
|| ch == '-' || ch == '+' || ch == ',' || ch == '.'
|
|
|| ch == '\'' || ch == ':' || ch == ';' || ch == '/') {
|
|
symbol = ch;
|
|
}
|
|
}
|
|
}
|
|
key->number = digit ? digit : symbol;
|
|
}
|
|
return NO_ERROR;
|
|
}
|
|
|
|
status_t KeyCharacterMap::Parser::parseModifier(const String8& token, int32_t* outMetaState) {
|
|
if (token == "base") {
|
|
*outMetaState = 0;
|
|
return NO_ERROR;
|
|
}
|
|
|
|
int32_t combinedMeta = 0;
|
|
|
|
const char* str = token.string();
|
|
const char* start = str;
|
|
for (const char* cur = str; ; cur++) {
|
|
char ch = *cur;
|
|
if (ch == '+' || ch == '\0') {
|
|
size_t len = cur - start;
|
|
int32_t metaState = 0;
|
|
for (size_t i = 0; i < sizeof(modifiers) / sizeof(Modifier); i++) {
|
|
if (strlen(modifiers[i].label) == len
|
|
&& strncmp(modifiers[i].label, start, len) == 0) {
|
|
metaState = modifiers[i].metaState;
|
|
break;
|
|
}
|
|
}
|
|
if (!metaState) {
|
|
return BAD_VALUE;
|
|
}
|
|
if (combinedMeta & metaState) {
|
|
ALOGE("%s: Duplicate modifier combination '%s'.",
|
|
mTokenizer->getLocation().string(), token.string());
|
|
return BAD_VALUE;
|
|
}
|
|
|
|
combinedMeta |= metaState;
|
|
start = cur + 1;
|
|
|
|
if (ch == '\0') {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
*outMetaState = combinedMeta;
|
|
return NO_ERROR;
|
|
}
|
|
|
|
status_t KeyCharacterMap::Parser::parseCharacterLiteral(char16_t* outCharacter) {
|
|
char ch = mTokenizer->nextChar();
|
|
if (ch != '\'') {
|
|
goto Error;
|
|
}
|
|
|
|
ch = mTokenizer->nextChar();
|
|
if (ch == '\\') {
|
|
// Escape sequence.
|
|
ch = mTokenizer->nextChar();
|
|
if (ch == 'n') {
|
|
*outCharacter = '\n';
|
|
} else if (ch == 't') {
|
|
*outCharacter = '\t';
|
|
} else if (ch == '\\') {
|
|
*outCharacter = '\\';
|
|
} else if (ch == '\'') {
|
|
*outCharacter = '\'';
|
|
} else if (ch == '"') {
|
|
*outCharacter = '"';
|
|
} else if (ch == 'u') {
|
|
*outCharacter = 0;
|
|
for (int i = 0; i < 4; i++) {
|
|
ch = mTokenizer->nextChar();
|
|
int digit;
|
|
if (ch >= '0' && ch <= '9') {
|
|
digit = ch - '0';
|
|
} else if (ch >= 'A' && ch <= 'F') {
|
|
digit = ch - 'A' + 10;
|
|
} else if (ch >= 'a' && ch <= 'f') {
|
|
digit = ch - 'a' + 10;
|
|
} else {
|
|
goto Error;
|
|
}
|
|
*outCharacter = (*outCharacter << 4) | digit;
|
|
}
|
|
} else {
|
|
goto Error;
|
|
}
|
|
} else if (ch >= 32 && ch <= 126 && ch != '\'') {
|
|
// ASCII literal character.
|
|
*outCharacter = ch;
|
|
} else {
|
|
goto Error;
|
|
}
|
|
|
|
ch = mTokenizer->nextChar();
|
|
if (ch != '\'') {
|
|
goto Error;
|
|
}
|
|
|
|
// Ensure that we consumed the entire token.
|
|
if (mTokenizer->nextToken(WHITESPACE).isEmpty()) {
|
|
return NO_ERROR;
|
|
}
|
|
|
|
Error:
|
|
ALOGE("%s: Malformed character literal.", mTokenizer->getLocation().string());
|
|
return BAD_VALUE;
|
|
}
|
|
|
|
} // namespace android
|