blob: 95daceee74e20801d32f8aa5f7b82ca4e10803b9 [file] [log] [blame]
/* Copyright (C) 2007-2008 The Android Open Source Project
**
** This software is licensed under the terms of the GNU General Public
** License version 2, as published by the Free Software Foundation, and
** may be copied, distributed, and modified under those terms.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
*/
#include "android/utils/path.h"
#include "android/utils/misc.h"
#include "android/utils/debug.h"
#include "android/utils/system.h"
#include "android/charmap.h"
#include <stdio.h>
#include <errno.h>
/* Parses .kcm file producing key characters map.
* .kcm file parsed by this module is expected to contain 4 types of
* lines:
* 1. An empty line (containing no characters, or only space or tab
* characters).
* 2. A comment line (begins with '#')
* 3. A type section line (begins with '[')
* 4. Character map line, formatted as such:
* Key code value, followed by one or more space or tab characters.
* Display value, followed by one or more space or tab characters.
* Number value, followed by one or more space or tab characters.
* Base value, followed by one or more space or tab characters.
* Caps value, followed by one or more space or tab characters.
* Fn value, followed by one or more space or tab characters.
* Caps_fn value, followed by one or more space or tab characters.
* All values, except for the key code value must be either in character
* form ('X', where X is the value), or in hexadecimal form (0xXXXX, where
* XXXX is hexadecimal representation of the value). Note that if value is
* in hexadecimal form, it must not exceed value that can be contained in
* variable of 'unsigned short' type.
* Bellow are a couple of examples of valid .kcm file lines:
* # keycode display number base caps fn caps_fn
* A 'A' '2' 'a' 'A' '#' 0x00
* PERIOD '.' '.' '.' ':' ':' 0x2026
* SPACE 0x20 0x20 0x20 0x20 0xEF01 0xEF01
*/
/* Maximum length of a line expected in .kcm file. */
#define KCM_MAX_LINE_LEN 1024
/* Maximum length of a token in a key map line. */
#define KCM_MAX_TOKEN_LEN 512
/* Maps symbol name from .kcm file to a keycode value. */
typedef struct AKeycodeMapEntry {
/* Symbol name from .kcm file. */
const char* key_name;
/* Key code value for the symbol. */
int key_code;
} AKeycodeMapEntry;
/* Result of parsing a line in a .kcm file. */
typedef enum {
/* Line format was bad. */
BAD_FORMAT,
/* Line had been skipped (an empty line, or a comment, etc.). */
SKIP_LINE,
/* Line represents an entry in the key map. */
KEY_ENTRY,
} ParseStatus;
static const AKeycodeMapEntry keycode_map[] = {
/* Symbol Key code */
{ "A", kKeyCodeA },
{ "B", kKeyCodeB },
{ "C", kKeyCodeC },
{ "D", kKeyCodeD },
{ "E", kKeyCodeE },
{ "F", kKeyCodeF },
{ "G", kKeyCodeG },
{ "H", kKeyCodeH },
{ "I", kKeyCodeI },
{ "J", kKeyCodeJ },
{ "K", kKeyCodeK },
{ "L", kKeyCodeL },
{ "M", kKeyCodeM },
{ "N", kKeyCodeN },
{ "O", kKeyCodeO },
{ "P", kKeyCodeP },
{ "Q", kKeyCodeQ },
{ "R", kKeyCodeR },
{ "S", kKeyCodeS },
{ "T", kKeyCodeT },
{ "U", kKeyCodeU },
{ "V", kKeyCodeV },
{ "W", kKeyCodeW },
{ "X", kKeyCodeX },
{ "Y", kKeyCodeY },
{ "Z", kKeyCodeZ },
{ "0", kKeyCode0 },
{ "1", kKeyCode1 },
{ "2", kKeyCode2 },
{ "3", kKeyCode3 },
{ "4", kKeyCode4 },
{ "5", kKeyCode5 },
{ "6", kKeyCode6 },
{ "7", kKeyCode7 },
{ "8", kKeyCode8 },
{ "9", kKeyCode9 },
{ "COMMA", kKeyCodeComma },
{ "PERIOD", kKeyCodePeriod },
{ "AT", kKeyCodeAt },
{ "SLASH", kKeyCodeSlash },
{ "SPACE", kKeyCodeSpace },
{ "ENTER", kKeyCodeNewline },
{ "TAB", kKeyCodeTab },
{ "GRAVE", kKeyCodeGrave },
{ "MINUS", kKeyCodeMinus },
{ "EQUALS", kKeyCodeEquals },
{ "LEFT_BRACKET", kKeyCodeLeftBracket },
{ "RIGHT_BRACKET", kKeyCodeRightBracket },
{ "BACKSLASH", kKeyCodeBackslash },
{ "SEMICOLON", kKeyCodeSemicolon },
{ "APOSTROPHE", kKeyCodeApostrophe },
{ "STAR", kKeyCodeStar },
{ "POUND", kKeyCodePound },
{ "PLUS", kKeyCodePlus },
{ "DEL", kKeyCodeDel },
};
/* the following is automatically generated by the 'gen-charmap.py' script
* do not touch. the generation command was:
* gen-charmap.py qwerty2.kcm
*/
static const AKeyEntry _qwerty2_keys[] =
{
/* keycode base caps fn caps+fn number */
{ kKeyCodeA , 'a', 'A', 'a', 'A', 'a' },
{ kKeyCodeB , 'b', 'B', 'b', 'B', 'b' },
{ kKeyCodeC , 'c', 'C', 0x00e7, 0x00E7, 'c' },
{ kKeyCodeD , 'd', 'D', '\'', '\'', '\'' },
{ kKeyCodeE , 'e', 'E', '"', 0x0301, '"' },
{ kKeyCodeF , 'f', 'F', '[', '[', '[' },
{ kKeyCodeG , 'g', 'G', ']', ']', ']' },
{ kKeyCodeH , 'h', 'H', '<', '<', '<' },
{ kKeyCodeI , 'i', 'I', '-', 0x0302, '-' },
{ kKeyCodeJ , 'j', 'J', '>', '>', '>' },
{ kKeyCodeK , 'k', 'K', ';', '~', ';' },
{ kKeyCodeL , 'l', 'L', ':', '`', ':' },
{ kKeyCodeM , 'm', 'M', '%', 0x00, '%' },
{ kKeyCodeN , 'n', 'N', 0x00, 0x0303, 'n' },
{ kKeyCodeO , 'o', 'O', '+', '+', '+' },
{ kKeyCodeP , 'p', 'P', '=', 0x00A5, '=' },
{ kKeyCodeQ , 'q', 'Q', '|', 0x0300, '|' },
{ kKeyCodeR , 'r', 'R', '`', 0x20AC, '`' },
{ kKeyCodeS , 's', 'S', '\\', 0x00DF, '\\' },
{ kKeyCodeT , 't', 'T', '{', 0x00A3, '}' },
{ kKeyCodeU , 'u', 'U', '_', 0x0308, '_' },
{ kKeyCodeV , 'v', 'V', 'v', 'V', 'v' },
{ kKeyCodeW , 'w', 'W', '~', '~', '~' },
{ kKeyCodeX , 'x', 'X', 'x', 'X', 'x' },
{ kKeyCodeY , 'y', 'Y', '}', 0x00A1, '}' },
{ kKeyCodeZ , 'z', 'Z', 'z', 'Z', 'z' },
{ kKeyCodeComma , ',', '<', ',', ',', ',' },
{ kKeyCodePeriod , '.', '>', '.', 0x2026, '.' },
{ kKeyCodeAt , '@', '@', '@', 0x2022, '@' },
{ kKeyCodeSlash , '/', '?', '?', '?', '/' },
{ kKeyCodeSpace , 0x20, 0x20, 0x9, 0x9, 0x20 },
{ kKeyCodeNewline , 0xa, 0xa, 0xa, 0xa, 0xa },
{ kKeyCode0 , '0', ')', ')', ')', '0' },
{ kKeyCode1 , '1', '!', '!', '!', '1' },
{ kKeyCode2 , '2', '@', '@', '@', '2' },
{ kKeyCode3 , '3', '#', '#', '#', '3' },
{ kKeyCode4 , '4', '$', '$', '$', '4' },
{ kKeyCode5 , '5', '%', '%', '%', '5' },
{ kKeyCode6 , '6', '^', '^', '^', '6' },
{ kKeyCode7 , '7', '&', '&', '&', '7' },
{ kKeyCode8 , '8', '*', '*', '*', '8' },
{ kKeyCode9 , '9', '(', '(', '(', '9' },
{ kKeyCodeTab , 0x9, 0x9, 0x9, 0x9, 0x9 },
{ kKeyCodeGrave , '`', '~', '`', '~', '`' },
{ kKeyCodeMinus , '-', '_', '-', '_', '-' },
{ kKeyCodeEquals , '=', '+', '=', '+', '=' },
{ kKeyCodeLeftBracket , '[', '{', '[', '{', '[' },
{ kKeyCodeRightBracket , ']', '}', ']', '}', ']' },
{ kKeyCodeBackslash , '\\', '|', '\\', '|', '\\' },
{ kKeyCodeSemicolon , ';', ':', ';', ':', ';' },
{ kKeyCodeApostrophe , '\'', '"', '\'', '"', '\'' },
};
static const AKeyCharmap _default_charmap =
{
_qwerty2_keys,
51,
"qwerty2"
};
/* Custom character map created with -charmap option. */
static AKeyCharmap android_custom_charmap = { 0 };
static const AKeyCharmap* android_charmap = &_default_charmap;
/* Checks if a character represents an end of the line.
* Returns a non-zero value if ch is an EOL character. Returns
* zero value if ch is not an EOL character.
*/
static int
kcm_is_eol(char ch) {
// EOLs are 0, \r and \n chars.
return ('\0' == ch) || ('\n' == ch) || ('\r' == ch);
}
/* Checks if a character represents a token separator.
* Returns a non-zero value if ch is a token separator.
* Returns zero value if ch is not a token separator.
*/
static int
kcm_is_token_separator(char ch) {
// Spaces and tabs are the only separators allowed
// between tokens in .kcm files.
return (' ' == ch) || ('\t' == ch);
}
/* Checks if a character represents a path separator.
* Returns a non-zero value if ch is a path separator.
* Returns zero value if ch is not a path separator.
*/
static int
kcm_is_path_separator(char ch) {
#ifdef _WIN32
return '/' == ch || '\\' == ch;
#else
return '/' == ch;
#endif // _WIN32
}
/* Skips space separators in a string.
* str - string to advance past space separators.
* Returns pointer to the first character in the string, that is
* not a space separator. Note that this routine may return
* pointer to EOL in case if all characters in the string were
* space separators.
*/
static const char*
kcm_skip_spaces(const char* str) {
while (!kcm_is_eol(*str) && kcm_is_token_separator(*str)) {
str++;
}
return str;
}
/* Advances string to the first space separator character.
* str - string to advance.
* Returns pointer to the first space separator character in the string.
* Note that this routine may return pointer to EOL in case if all
* characters in the string were not space separators.
*/
static const char*
kcm_skip_non_spaces(const char* str) {
while (!kcm_is_eol(*str) && !kcm_is_token_separator(*str)) {
str++;
}
return str;
}
/* Gets first token from a string.
* line - String to get token from. End of the string should be
* determined using kcm_is_eol() routine.
* token - String where to copy token. Token, copied to this
* string will be zero-terminated. Note that buffer for the
* token string must be large enough to fit token of any size.
* max_token_len - character size of the buffer addressed by
* the 'token' parameter.
* Returns NULL if there were no tokens found in the string, or
* a pointer to the line string, advanced past the found token.
*/
static const char*
kcm_get_token(const char* line, char* token, size_t max_token_len) {
// Pass spaces and tabs.
const char* token_starts = kcm_skip_spaces(line);
// Advance to next space.
const char* token_ends = kcm_skip_non_spaces(token_starts);
// Calc token length
size_t token_len = token_ends - token_starts;
if ((0 == token_len) || (token_len >= max_token_len)) {
return NULL;
}
memcpy(token, token_starts, token_len);
token[token_len] = '\0';
return token_ends;
}
/* Checks if token represents a comment.
* Returns non-zero value if token represents a comment, or zero otherwise.
*/
static int
kcm_is_token_comment(const char* token) {
return '#' == *token;
}
/* Converts a key name to a key code as defined by AndroidKeyCode enum.
* key_name - Key name to convert.
* key_code - Upon success contains key code value for the key_name.
* Returns a zero value on success, or -1 if key code was not found
* for the given key_name.
*/
static int
kcm_get_key_code(const char* key_name, unsigned short* key_code) {
int n;
// Iterate through the key code map, matching key names.
for (n = 0; n < sizeof(keycode_map) / sizeof(keycode_map[0]); n++) {
if (0 == strcmp(key_name, keycode_map[n].key_name)) {
*key_code = keycode_map[n].key_code;
return 0;
}
}
return -1;
}
/* Gets unsigned short hexadecimal value for a token.
* token - Token to get hexadecimal value for. Note that this
* routine expects a "clean" (i.e. no "0x" prefix) hex number
* represented by the token string.
* val - Upon success contains hexadecimal value for the token.
* Returns a zero value on success, or -1 on error.
*/
static int
kcm_get_ushort_hex_val(const char* token, unsigned short* val) {
int hex_val = hex2int((const uint8_t*)token, strlen(token));
// Make sure token format was ok and value doesn't exceed unsigned short.
if (-1 == hex_val || 0 != (hex_val & ~0xFFFF)) {
return -1;
}
*val = (unsigned short)hex_val;
return 0;
}
/* Gets a character or hexadecimal value represented by a token.
* token - Token to get value from.
* val - Upon success will contain a character or hexadecimal
* value represented by a token.
* Returns a zero value on success, or -1 on error.
*/
static int
kcm_get_char_or_hex_val(const char* token, unsigned short* val) {
// For chars token must begin with ' followed by character followed by '
if ('\'' == *token) {
if ('\0' == token[1] || '\'' != token[2] || '\0' != token[3]) {
return 0;
}
*val = token[1];
return 0;
} else {
// Make sure that hex token is prefixed with "0x"
if (('0' != *token) || ('x' != token[1])) {
return -1;
}
// Past 0x
return kcm_get_ushort_hex_val(token + 2, val);
}
}
/* Gets first token for the line and calculates its value.
* line - Line to get token's value from.
* val - Upon success will contain a character or hexadecimal
* value represented by the first token in the line.
* returns NULL on error, or a pointer to the line string,
* advanced past the found token.
*/
static const char*
kcm_get_char_or_hex_token_value(const char* line, unsigned short* val) {
char token[KCM_MAX_TOKEN_LEN];
line = kcm_get_token(line, token, KCM_MAX_TOKEN_LEN);
if (NULL != line) {
// Token must be a char, or a hex number.
if (kcm_get_char_or_hex_val(token, val)) {
return NULL;
}
}
return line;
}
/* Parses a line in .kcm file extracting key information.
* line - Line in .kcm file to parse.
* line_index - Index of the parsing line in .kcm file.
* key_entry - Upon success contains key information extracted from
* the line.
* kcm_file_path - Path to the charmap file, where paresed line was taken from.
* returns BAD_FORMAT if line format was not recognized, SKIP_LINE if line
* format was ok, but it didn't contain key information, or KEY_ENTRY
* if key information was successfuly extracted from the line.
*/
static ParseStatus
kcm_parse_line(const char* line,
int line_index,
AKeyEntry* key_entry,
const char* kcm_file_path) {
char token[KCM_MAX_TOKEN_LEN];
unsigned short disp;
// Get first token, and see if it's an empty, or a comment line.
line = kcm_get_token(line, token, KCM_MAX_TOKEN_LEN);
if ((NULL == line) || kcm_is_token_comment(token)) {
// Empty line, or a comment.
return SKIP_LINE;
}
// Here we expect either [type=XXXX], or a key string.
if ('[' == token[0]) {
return SKIP_LINE;
}
// It must be a key string.
// First token is key code.
if (kcm_get_key_code(token, &key_entry->code)) {
derror("Invalid format of charmap file %s. Unknown key %s in line %d",
kcm_file_path, token, line_index);
return BAD_FORMAT;
}
// 2-nd token is display character, which is ignored.
line = kcm_get_char_or_hex_token_value(line, &disp);
if (NULL == line) {
derror("Invalid format of charmap file %s. Invalid display value in line %d",
kcm_file_path, line_index);
return BAD_FORMAT;
}
// 3-rd token is number.
line = kcm_get_char_or_hex_token_value(line, &key_entry->number);
if (NULL == line) {
derror("Invalid format of charmap file %s. Invalid number value in line %d",
kcm_file_path, line_index);
return BAD_FORMAT;
}
// 4-th token is base.
line = kcm_get_char_or_hex_token_value(line, &key_entry->base);
if (NULL == line) {
derror("Invalid format of charmap file %s. Invalid base value in line %d",
kcm_file_path, line_index);
return BAD_FORMAT;
}
// 5-th token is caps.
line = kcm_get_char_or_hex_token_value(line, &key_entry->caps);
if (NULL == line) {
derror("Invalid format of charmap file %s. Invalid caps value in line %d",
kcm_file_path, line_index);
return BAD_FORMAT;
}
// 6-th token is fn.
line = kcm_get_char_or_hex_token_value(line, &key_entry->fn);
if (NULL == line) {
derror("Invalid format of charmap file %s. Invalid fn value in line %d",
kcm_file_path, line_index);
return BAD_FORMAT;
}
// 7-th token is caps_fn.
line = kcm_get_char_or_hex_token_value(line, &key_entry->caps_fn);
if (NULL == line) {
derror("Invalid format of charmap file %s. Invalid caps_fn value in line %d",
kcm_file_path, line_index);
return BAD_FORMAT;
}
// Make sure that line doesn't contain anything else,
// except (may be) a comment token.
line = kcm_get_token(line, token, KCM_MAX_TOKEN_LEN);
if ((NULL == line) || kcm_is_token_comment(token)) {
return KEY_ENTRY;
} else {
derror("Invalid format of charmap file %s in line %d",
kcm_file_path, line_index);
return BAD_FORMAT;
}
}
void
kcm_extract_charmap_name(const char* kcm_file_path,
char* charmap_name,
int max_len) {
const char* ext_separator;
size_t to_copy;
// Initialize charmap name with name of .kcm file.
// First, get file name from the full path to .kcm file.
const char* file_name = kcm_file_path + strlen(kcm_file_path);
while (!kcm_is_path_separator(*file_name) &&
(file_name != kcm_file_path)) {
file_name--;
}
if (kcm_is_path_separator(*file_name)) {
file_name++;
}
// Cut off file name extension.
ext_separator = strrchr(file_name, '.');
if (NULL == ext_separator) {
// "filename" is legal name.
ext_separator = file_name + strlen(file_name);
} else if (ext_separator == file_name) {
// ".filename" is legal name too. In this case we will use
// "filename" as our custom charmap name.
file_name++;
ext_separator = file_name + strlen(file_name);
}
// Copy file name to charmap name.
to_copy = ext_separator - file_name;
if (to_copy > (max_len - 1)) {
to_copy = max_len - 1;
}
memcpy(charmap_name, file_name, to_copy);
charmap_name[to_copy] = '\0';
}
/* Extracts charmap name from .kcm file name,
* and saves it into char_map as its name.
*/
static void
kcm_get_charmap_name(const char* kcm_file_path, AKeyCharmap* char_map) {
kcm_extract_charmap_name(kcm_file_path, char_map->name,
sizeof(char_map->name));
}
/* Parses .kcm file producing key characters map.
* See comments to this module for .kcm file format information.
* This routine checks format only for character map lines. It will not check
* format for empty lines, comments, and type section lines.
* Note that line length in .kcm file should not exceed 1024 characters,
* including newline character.
*
* Parameters:
* kcm_file_path - Full path to the .kcm file to parse.
* char_map - Upon success will contain initialized characters map.
* Returns a zero value on success, or -1 on failure.
*/
static int
parse_kcm_file(const char* kcm_file_path, AKeyCharmap* char_map) {
// A line read from .kcm file.
char line[KCM_MAX_LINE_LEN];
// Return code.
int err = 0;
// Number of the currently parsed line.
int cur_line = 1;
// Initial size of the charmap's array of keys.
int map_size = 52;
FILE* kcm_file;
char_map->num_entries = 0;
char_map->entries = 0;
kcm_file = fopen(kcm_file_path, "r");
if (NULL == kcm_file) {
derror("Unable to open charmap file %s : %s",
kcm_file_path, strerror(errno));
return -1;
}
// Calculate charmap name.
kcm_get_charmap_name(kcm_file_path, char_map);
// Preallocate map.
char_map->num_entries = 0;
AARRAY_NEW0(char_map->entries, map_size);
// Line by line parse the file.
for (; 0 != fgets(line, sizeof(line), kcm_file); cur_line++) {
AKeyEntry key_entry;
ParseStatus parse_res =
kcm_parse_line(line, cur_line, &key_entry, kcm_file_path);
if (BAD_FORMAT == parse_res) {
err = -1;
break;
} else if (KEY_ENTRY == parse_res) {
AKeyEntry* entries;
// Key information has been extracted. Add it to the map.
// Lets see if we need to reallocate map.
if (map_size == char_map->num_entries) {
AKeyEntry* entries = (AKeyEntry*)char_map->entries;
map_size += 10;
AARRAY_RENEW(entries, map_size);
char_map->entries = (const AKeyEntry*)entries;
}
entries = (AKeyEntry*)char_map->entries;
entries[char_map->num_entries] = key_entry;
char_map->num_entries++;
}
}
if (!err) {
// Make sure we exited the loop on EOF condition. Any other
// condition is an error.
if (0 == feof(kcm_file)) {
err = -1;
}
if (err) {
derror("Error reading charmap file %s : %s",
kcm_file_path, strerror(errno));
}
}
fclose(kcm_file);
if (err) {
// Cleanup on failure.
if (0 != char_map->entries) {
AFREE((void*)char_map->entries);
char_map->entries = 0;
}
char_map->num_entries = 0;
}
return err;
}
int
android_charmap_setup(const char* kcm_file_path) {
/* Return if we already loaded a charmap */
if (android_charmap != &_default_charmap || kcm_file_path == NULL)
return 0;
if (!parse_kcm_file(kcm_file_path, &android_custom_charmap)) {
// Here we have the default charmap and the custom one.
android_charmap = &android_custom_charmap;
} else {
derror("Unable to parse kcm file.");
return -1;
}
return 0;
}
void
android_charmap_done(void) {
if (android_charmap != &_default_charmap)
AFREE((void*)android_charmap->entries);
}
const AKeyCharmap*
android_get_charmap_by_name(const char* name) {
if (name != NULL) {
if (!strcmp(android_charmap->name, name))
return android_charmap;
if (!strcmp(_default_charmap.name, name))
return &_default_charmap;
}
return NULL;
}
int
android_charmap_reverse_map_unicode(const AKeyCharmap* cmap,
unsigned int unicode,
int down,
AKeycodeBuffer* keycodes)
{
int n;
if (unicode == 0)
return 0;
/* check base keys */
for (n = 0; n < cmap->num_entries; n++) {
if (cmap->entries[n].base == unicode) {
android_keycodes_add_key_event(keycodes, cmap->entries[n].code, down);
return 1;
}
}
/* check caps + keys */
for (n = 0; n < cmap->num_entries; n++) {
if (cmap->entries[n].caps == unicode) {
if (down) {
android_keycodes_add_key_event(keycodes, kKeyCodeCapLeft, down);
}
android_keycodes_add_key_event(keycodes, cmap->entries[n].code, down);
if (!down) {
android_keycodes_add_key_event(keycodes, kKeyCodeCapLeft, down);
}
return 2;
}
}
/* check fn + keys */
for (n = 0; n < cmap->num_entries; n++) {
if (cmap->entries[n].fn == unicode) {
if (down) {
android_keycodes_add_key_event(keycodes, kKeyCodeAltLeft, down);
}
android_keycodes_add_key_event(keycodes, cmap->entries[n].code, down);
if (!down) {
android_keycodes_add_key_event(keycodes, kKeyCodeAltLeft, down);
}
return 2;
}
}
/* check caps + fn + keys */
for (n = 0; n < cmap->num_entries; n++) {
if (cmap->entries[n].caps_fn == unicode) {
if (down) {
android_keycodes_add_key_event(keycodes, kKeyCodeAltLeft, down);
android_keycodes_add_key_event(keycodes, kKeyCodeCapLeft, down);
}
android_keycodes_add_key_event(keycodes, cmap->entries[n].code, down);
if (!down) {
android_keycodes_add_key_event(keycodes, kKeyCodeCapLeft, down);
android_keycodes_add_key_event(keycodes, kKeyCodeAltLeft, down);
}
return 3;
}
}
/* no match */
return 0;
}
const AKeyCharmap* android_get_default_charmap(void)
{
return &_default_charmap;
}
const AKeyCharmap* android_get_charmap(void)
{
return android_charmap;
}
const char* android_get_charmap_name(void)
{
return android_get_charmap()->name;
}