blob: 33717997e999e5077130e18e0e2b24787bc81395 [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/skin/keyboard.h"
#include "android/utils/debug.h"
#include "android/utils/bufprint.h"
#include "android/utils/system.h"
#include "android/android.h"
#include "android/keycode-array.h"
#include "android/charmap.h"
#define DEBUG 1
#if DEBUG
# define D(...) VERBOSE_PRINT(keys,__VA_ARGS__)
#else
# define D(...) ((void)0)
#endif
#define DEFAULT_ANDROID_CHARMAP "qwerty2"
/** LAST PRESSED KEYS
** a small buffer of last pressed keys, this is used to properly
** implement the Unicode keyboard mode (SDL key up event always have
** their .unicode field set to 0
**/
typedef struct {
int unicode; /* Unicode of last pressed key */
int sym; /* SDL key symbol value (e.g. SDLK_a) */
int mod; /* SDL key modifier value */
} LastKey;
#define MAX_LAST_KEYS 16
struct SkinKeyboard {
const AKeyCharmap* charmap;
SkinKeyset* kset;
char enabled;
char raw_keys;
char last_count;
SkinRotation rotation;
SkinKeyCommandFunc command_func;
void* command_opaque;
SkinKeyEventFunc press_func;
void* press_opaque;
LastKey last_keys[ MAX_LAST_KEYS ];
AKeycodeBuffer keycodes;
};
void
skin_keyboard_set_keyset( SkinKeyboard* keyboard, SkinKeyset* kset )
{
if (kset == NULL)
return;
if (keyboard->kset && keyboard->kset != android_keyset) {
skin_keyset_free(keyboard->kset);
}
keyboard->kset = kset;
}
void
skin_keyboard_set_rotation( SkinKeyboard* keyboard,
SkinRotation rotation )
{
keyboard->rotation = (rotation & 3);
}
void
skin_keyboard_on_command( SkinKeyboard* keyboard, SkinKeyCommandFunc cmd_func, void* cmd_opaque )
{
keyboard->command_func = cmd_func;
keyboard->command_opaque = cmd_opaque;
}
void
skin_keyboard_on_key_press( SkinKeyboard* keyboard, SkinKeyEventFunc press_func, void* press_opaque )
{
keyboard->press_func = press_func;
keyboard->press_opaque = press_opaque;
}
void
skin_keyboard_add_key_event( SkinKeyboard* kb,
unsigned code,
unsigned down )
{
android_keycodes_add_key_event(&kb->keycodes, code, down);
}
void
skin_keyboard_flush( SkinKeyboard* kb )
{
android_keycodes_flush(&kb->keycodes);
}
static void
skin_keyboard_cmd( SkinKeyboard* keyboard,
SkinKeyCommand command,
int param )
{
if (keyboard->command_func) {
keyboard->command_func( keyboard->command_opaque, command, param );
}
}
static LastKey*
skin_keyboard_find_last( SkinKeyboard* keyboard,
int sym )
{
LastKey* k = keyboard->last_keys;
LastKey* end = k + keyboard->last_count;
for ( ; k < end; k++ ) {
if (k->sym == sym)
return k;
}
return NULL;
}
static void
skin_keyboard_add_last( SkinKeyboard* keyboard,
int sym,
int mod,
int unicode )
{
LastKey* k = keyboard->last_keys + keyboard->last_count;
if (keyboard->last_count < MAX_LAST_KEYS) {
k->sym = sym;
k->mod = mod;
k->unicode = unicode;
keyboard->last_count += 1;
}
}
static void
skin_keyboard_remove_last( SkinKeyboard* keyboard,
int sym )
{
LastKey* k = keyboard->last_keys;
LastKey* end = k + keyboard->last_count;
for ( ; k < end; k++ ) {
if (k->sym == sym) {
/* we don't need a sorted array, so place the last
* element in place at the position of the removed
* one... */
k[0] = end[-1];
keyboard->last_count -= 1;
break;
}
}
}
static void
skin_keyboard_clear_last( SkinKeyboard* keyboard )
{
keyboard->last_count = 0;
}
static int
skin_keyboard_rotate_sym( SkinKeyboard* keyboard,
int sym )
{
switch (keyboard->rotation) {
case SKIN_ROTATION_90:
switch (sym) {
case SDLK_LEFT: sym = SDLK_DOWN; break;
case SDLK_RIGHT: sym = SDLK_UP; break;
case SDLK_UP: sym = SDLK_LEFT; break;
case SDLK_DOWN: sym = SDLK_RIGHT; break;
}
break;
case SKIN_ROTATION_180:
switch (sym) {
case SDLK_LEFT: sym = SDLK_RIGHT; break;
case SDLK_RIGHT: sym = SDLK_LEFT; break;
case SDLK_UP: sym = SDLK_DOWN; break;
case SDLK_DOWN: sym = SDLK_UP; break;
}
break;
case SKIN_ROTATION_270:
switch (sym) {
case SDLK_LEFT: sym = SDLK_UP; break;
case SDLK_RIGHT: sym = SDLK_DOWN; break;
case SDLK_UP: sym = SDLK_RIGHT; break;
case SDLK_DOWN: sym = SDLK_LEFT; break;
}
break;
default: ;
}
return sym;
}
static AndroidKeyCode
skin_keyboard_key_to_code( SkinKeyboard* keyboard,
unsigned sym,
int mod,
int down )
{
AndroidKeyCode code = 0;
int mod0 = mod;
SkinKeyCommand command;
/* first, handle the arrow keys directly */
/* rotate them if necessary */
sym = skin_keyboard_rotate_sym(keyboard, sym);
mod &= (KMOD_CTRL | KMOD_ALT | KMOD_SHIFT);
switch (sym) {
case SDLK_LEFT: code = kKeyCodeDpadLeft; break;
case SDLK_RIGHT: code = kKeyCodeDpadRight; break;
case SDLK_UP: code = kKeyCodeDpadUp; break;
case SDLK_DOWN: code = kKeyCodeDpadDown; break;
default: ;
}
if (code != 0) {
D("handling arrow (sym=%d mod=%d)", sym, mod);
if (!keyboard->raw_keys) {
int doCapL, doCapR, doAltL, doAltR;
if (!down) {
LastKey* k = skin_keyboard_find_last(keyboard, sym);
if (k != NULL) {
mod = k->mod;
skin_keyboard_remove_last( keyboard, sym );
}
} else {
skin_keyboard_add_last( keyboard, sym, mod, 0);
}
doCapL = (mod & 0x7ff) & KMOD_LSHIFT;
doCapR = (mod & 0x7ff) & KMOD_RSHIFT;
doAltL = (mod & 0x7ff) & KMOD_LALT;
doAltR = (mod & 0x7ff) & KMOD_RALT;
if (down) {
if (doAltL) skin_keyboard_add_key_event( keyboard, kKeyCodeAltLeft, 1 );
if (doAltR) skin_keyboard_add_key_event( keyboard, kKeyCodeAltRight, 1 );
if (doCapL) skin_keyboard_add_key_event( keyboard, kKeyCodeCapLeft, 1 );
if (doCapR) skin_keyboard_add_key_event( keyboard, kKeyCodeCapRight, 1 );
}
skin_keyboard_add_key_event(keyboard, code, down);
if (!down) {
if (doCapR) skin_keyboard_add_key_event( keyboard, kKeyCodeCapRight, 0 );
if (doCapL) skin_keyboard_add_key_event( keyboard, kKeyCodeCapLeft, 0 );
if (doAltR) skin_keyboard_add_key_event( keyboard, kKeyCodeAltRight, 0 );
if (doAltL) skin_keyboard_add_key_event( keyboard, kKeyCodeAltLeft, 0 );
}
code = 0;
}
return code;
}
/* special case for keypad keys, ignore them here if numlock is on */
if ((mod0 & KMOD_NUM) != 0) {
switch (sym) {
case SDLK_KP0:
case SDLK_KP1:
case SDLK_KP2:
case SDLK_KP3:
case SDLK_KP4:
case SDLK_KP5:
case SDLK_KP6:
case SDLK_KP7:
case SDLK_KP8:
case SDLK_KP9:
case SDLK_KP_PLUS:
case SDLK_KP_MINUS:
case SDLK_KP_MULTIPLY:
case SDLK_KP_DIVIDE:
case SDLK_KP_EQUALS:
case SDLK_KP_PERIOD:
case SDLK_KP_ENTER:
return 0;
}
}
/* now try all keyset combos */
command = skin_keyset_get_command( keyboard->kset, sym, mod );
if (command != SKIN_KEY_COMMAND_NONE) {
D("handling command %s from (sym=%d, mod=%d, str=%s)",
skin_key_command_to_str(command), sym, mod, skin_key_symmod_to_str(sym,mod));
skin_keyboard_cmd( keyboard, command, down );
return 0;
}
D("could not handle (sym=%d, mod=%d, str=%s)", sym, mod,
skin_key_symmod_to_str(sym,mod));
return -1;
}
/* this gets called only if the reverse unicode mapping didn't work
* or wasn't used (when in raw keys mode)
*/
static AndroidKeyCode
skin_keyboard_raw_key_to_code(SkinKeyboard* kb, unsigned sym, int down)
{
switch(sym){
case SDLK_1: return kKeyCode1;
case SDLK_2: return kKeyCode2;
case SDLK_3: return kKeyCode3;
case SDLK_4: return kKeyCode4;
case SDLK_5: return kKeyCode5;
case SDLK_6: return kKeyCode6;
case SDLK_7: return kKeyCode7;
case SDLK_8: return kKeyCode8;
case SDLK_9: return kKeyCode9;
case SDLK_0: return kKeyCode0;
case SDLK_q: return kKeyCodeQ;
case SDLK_w: return kKeyCodeW;
case SDLK_e: return kKeyCodeE;
case SDLK_r: return kKeyCodeR;
case SDLK_t: return kKeyCodeT;
case SDLK_y: return kKeyCodeY;
case SDLK_u: return kKeyCodeU;
case SDLK_i: return kKeyCodeI;
case SDLK_o: return kKeyCodeO;
case SDLK_p: return kKeyCodeP;
case SDLK_a: return kKeyCodeA;
case SDLK_s: return kKeyCodeS;
case SDLK_d: return kKeyCodeD;
case SDLK_f: return kKeyCodeF;
case SDLK_g: return kKeyCodeG;
case SDLK_h: return kKeyCodeH;
case SDLK_j: return kKeyCodeJ;
case SDLK_k: return kKeyCodeK;
case SDLK_l: return kKeyCodeL;
case SDLK_z: return kKeyCodeZ;
case SDLK_x: return kKeyCodeX;
case SDLK_c: return kKeyCodeC;
case SDLK_v: return kKeyCodeV;
case SDLK_b: return kKeyCodeB;
case SDLK_n: return kKeyCodeN;
case SDLK_m: return kKeyCodeM;
case SDLK_COMMA: return kKeyCodeComma;
case SDLK_PERIOD: return kKeyCodePeriod;
case SDLK_SPACE: return kKeyCodeSpace;
case SDLK_SLASH: return kKeyCodeSlash;
case SDLK_RETURN: return kKeyCodeNewline;
case SDLK_BACKSPACE: return kKeyCodeDel;
/* these are qwerty keys not on a device keyboard */
case SDLK_TAB: return kKeyCodeTab;
case SDLK_BACKQUOTE: return kKeyCodeGrave;
case SDLK_MINUS: return kKeyCodeMinus;
case SDLK_EQUALS: return kKeyCodeEquals;
case SDLK_LEFTBRACKET: return kKeyCodeLeftBracket;
case SDLK_RIGHTBRACKET: return kKeyCodeRightBracket;
case SDLK_BACKSLASH: return kKeyCodeBackslash;
case SDLK_SEMICOLON: return kKeyCodeSemicolon;
case SDLK_QUOTE: return kKeyCodeApostrophe;
case SDLK_RSHIFT: return kKeyCodeCapRight;
case SDLK_LSHIFT: return kKeyCodeCapLeft;
case SDLK_RMETA: return kKeyCodeSym;
case SDLK_LMETA: return kKeyCodeSym;
case SDLK_RALT: return kKeyCodeAltRight;
case SDLK_LALT: return kKeyCodeAltLeft;
case SDLK_RCTRL: return kKeyCodeSym;
case SDLK_LCTRL: return kKeyCodeSym;
default:
/* fprintf(stderr,"* unknown sdl keysym %d *\n", sym); */
return -1;
}
}
static void
skin_keyboard_do_key_event( SkinKeyboard* kb,
AndroidKeyCode code,
int down )
{
if (kb->press_func) {
kb->press_func( kb->press_opaque, code, down );
}
skin_keyboard_add_key_event(kb, code, down);
}
int
skin_keyboard_process_unicode_event( SkinKeyboard* kb, unsigned int unicode, int down )
{
return android_charmap_reverse_map_unicode(kb->charmap, unicode, down,
&kb->keycodes);
}
void
skin_keyboard_enable( SkinKeyboard* keyboard,
int enabled )
{
keyboard->enabled = enabled;
if (enabled) {
SDL_EnableUNICODE(!keyboard->raw_keys);
SDL_EnableKeyRepeat(0,0);
}
}
void
skin_keyboard_process_event( SkinKeyboard* kb, SDL_Event* ev, int down )
{
unsigned code;
int unicode = ev->key.keysym.unicode;
int sym = ev->key.keysym.sym;
int mod = ev->key.keysym.mod;
/* ignore key events if we're not enabled */
if (!kb->enabled) {
printf( "ignoring key event sym=%d mod=0x%x unicode=%d\n",
sym, mod, unicode );
return;
}
/* first, try the keyboard-mode-independent keys */
code = skin_keyboard_key_to_code( kb, sym, mod, down );
if (code == 0)
return;
if ((int)code > 0) {
skin_keyboard_do_key_event(kb, code, down);
skin_keyboard_flush(kb);
return;
}
/* Ctrl-K is used to switch between 'unicode' and 'raw' modes */
if (sym == SDLK_k)
{
int mod2 = mod & 0x7ff;
if ( mod2 == KMOD_LCTRL || mod2 == KMOD_RCTRL ) {
if (down) {
skin_keyboard_clear_last(kb);
kb->raw_keys = !kb->raw_keys;
SDL_EnableUNICODE(!kb->raw_keys);
D( "switching keyboard to %s mode", kb->raw_keys ? "raw" : "unicode" );
}
return;
}
}
if (!kb->raw_keys) {
/* ev->key.keysym.unicode is only valid on keydown events, and will be 0
* on the corresponding keyup ones, so remember the set of last pressed key
* syms to "undo" the job
*/
if ( !down && unicode == 0 ) {
LastKey* k = skin_keyboard_find_last(kb, sym);
if (k != NULL) {
unicode = k->unicode;
skin_keyboard_remove_last(kb, sym);
}
}
}
if (!kb->raw_keys &&
skin_keyboard_process_unicode_event( kb, unicode, down ) > 0)
{
if (down)
skin_keyboard_add_last( kb, sym, mod, unicode );
skin_keyboard_flush( kb );
return;
}
code = skin_keyboard_raw_key_to_code( kb, sym, down );
if ( !kb->raw_keys &&
(code == kKeyCodeAltLeft || code == kKeyCodeAltRight ||
code == kKeyCodeCapLeft || code == kKeyCodeCapRight ||
code == kKeyCodeSym) )
return;
if (code == -1) {
D("ignoring keysym %d", sym );
} else if (code > 0) {
skin_keyboard_do_key_event(kb, code, down);
skin_keyboard_flush(kb);
}
}
static SkinKeyboard*
skin_keyboard_create_from_charmap_name(const char* charmap_name,
int use_raw_keys)
{
SkinKeyboard* kb;
ANEW0(kb);
kb->charmap = android_get_charmap_by_name(charmap_name);
if (!kb->charmap) {
// Charmap name was not found. Default to "qwerty2" */
kb->charmap = android_get_charmap_by_name(DEFAULT_ANDROID_CHARMAP);
fprintf(stderr, "### warning, skin requires unknown '%s' charmap, reverting to '%s'\n",
charmap_name, kb->charmap->name );
}
kb->raw_keys = use_raw_keys;
kb->enabled = 0;
/* add default keyset */
if (android_keyset)
kb->kset = android_keyset;
else
kb->kset = skin_keyset_new_from_text( skin_keyset_get_default() );
return kb;
}
SkinKeyboard*
skin_keyboard_create( const char* kcm_file_path, int use_raw_keys )
{
const char* charmap_name = DEFAULT_ANDROID_CHARMAP;
char cmap_buff[AKEYCHARMAP_NAME_SIZE];
if (kcm_file_path != NULL) {
kcm_extract_charmap_name(kcm_file_path, cmap_buff, sizeof cmap_buff);
charmap_name = cmap_buff;
}
return skin_keyboard_create_from_charmap_name(charmap_name, use_raw_keys);
}
void
skin_keyboard_free( SkinKeyboard* keyboard )
{
if (keyboard) {
AFREE(keyboard);
}
}