blob: 190ad729f19e6a0625e26f4be0b375309e52ac7b [file] [log] [blame]
/* Copyright (C) 2014 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/event.h"
#include "android/skin/keycode.h"
#include "android/skin/linux_keycodes.h"
#include <SDL.h>
#include <stdio.h>
#ifdef _WIN32
#include <windows.h>
#endif
#define DEBUG 0
#if DEBUG
# define D(...) printf(__VA_ARGS__)
#else
# define D(...) ((void)0)
#endif
// Returns true if this is a (sym,mod) pair that corresponds to one of
// the non-Unicode keys. The intended goal is for the caller to ignore
// non-special keys because they will generate a TEXTINPUT event too.
//
// Note that SDL2 uses direct Unicode values for keycodes that match the
// correspond character / glyph. For others, the SDLK_SCANCODE_MASK bitmask
// is or-ed (see SDL_keycode.h).
static bool sdl_is_special_keypair(int sdl_sym, int sdl_mod) {
// Ctrl-something is always a special key combination
// Same with the GUI key.
if (sdl_mod & (KMOD_CTRL | KMOD_GUI)) {
return true;
}
// Alt-something will always generate a TEXTINPUT so it needs to be
// treated as non-special.
if (sdl_mod & KMOD_ALT) {
return false;
}
if ((sdl_sym & SDLK_SCANCODE_MASK) != 0) {
// This is a non-Unicode key, so it's special.
return true;
}
if (sdl_sym < 32) {
// TAB, ENTER, ESC, etc, are all special.
return true;
}
return false;
}
static bool sdl_is_keypad_keycode(int keycode) {
static int kp_codes[] = {
SDLK_KP_DIVIDE,
SDLK_KP_MULTIPLY,
SDLK_KP_MINUS,
SDLK_KP_PLUS,
SDLK_KP_ENTER,
SDLK_KP_1,
SDLK_KP_2,
SDLK_KP_3,
SDLK_KP_4,
SDLK_KP_5,
SDLK_KP_6,
SDLK_KP_7,
SDLK_KP_8,
SDLK_KP_9,
SDLK_KP_0,
SDLK_KP_PERIOD,
};
static const size_t kp_codes_size = sizeof(kp_codes) / sizeof(kp_codes[0]);
size_t n;
for (n = 0; n < kp_codes_size; ++n) {
if (keycode == kp_codes[n]) {
return true;
}
}
return false;
}
// Convert an SDL_SCANCODE_XXX code into the corresponding Linux keycode value.
// On failure, return -1.
static int sdl_scancode_to_key_code(int scancode) {
#define KK(x,y) { SDL_SCANCODE_ ## x, KEY_ ## y }
#define K1(x) KK(x,x)
static const struct {
int scancode;
int keycode;
} kConvert[] = {
K1(LEFT),
K1(RIGHT),
K1(UP),
K1(DOWN),
KK(KP_0, KP0),
KK(KP_1, KP1),
KK(KP_2, KP2),
KK(KP_3, KP3),
KK(KP_4, KP4),
KK(KP_5, KP5),
KK(KP_6, KP6),
KK(KP_7, KP7),
KK(KP_8, KP8),
KK(KP_9, KP9),
KK(KP_MINUS,KPMINUS),
KK(KP_PLUS,KPPLUS),
KK(KP_MULTIPLY,KPASTERISK),
KK(KP_DIVIDE,KPSLASH),
KK(KP_EQUALS,KPEQUAL),
KK(KP_PERIOD,KPDOT),
KK(KP_ENTER,KPENTER),
KK(ESCAPE,ESC),
K1(0),
K1(1),
K1(2),
K1(3),
K1(4),
K1(5),
K1(6),
K1(7),
K1(8),
K1(9),
K1(MINUS),
KK(EQUALS,EQUAL),
K1(BACKSPACE),
K1(HOME),
K1(F1),
K1(F2),
K1(F3),
K1(F4),
K1(F5),
K1(F6),
K1(F7),
K1(F8),
K1(F9),
K1(F10),
K1(F11),
K1(F12),
K1(A),
K1(B),
K1(C),
K1(D),
K1(E),
K1(F),
K1(G),
K1(H),
K1(I),
K1(J),
K1(K),
K1(L),
K1(M),
K1(N),
K1(O),
K1(P),
K1(Q),
K1(R),
K1(S),
K1(T),
K1(U),
K1(V),
K1(W),
K1(X),
K1(Y),
K1(Z),
K1(COMMA),
KK(PERIOD,DOT),
K1(SPACE),
K1(SLASH),
KK(RETURN,ENTER),
K1(TAB),
K1(GRAVE),
KK(LEFTBRACKET,LEFTBRACE),
KK(RIGHTBRACKET,RIGHTBRACE),
K1(BACKSLASH),
K1(SEMICOLON),
K1(APOSTROPHE),
KK(RSHIFT,RIGHTSHIFT),
KK(LSHIFT,LEFTSHIFT),
KK(RGUI,COMPOSE),
KK(LGUI,COMPOSE),
KK(RALT,RIGHTALT),
KK(LALT,LEFTALT),
KK(RCTRL,RIGHTCTRL),
KK(LCTRL,LEFTCTRL),
KK(NUMLOCKCLEAR, NUMLOCK),
};
const size_t kConvertSize = sizeof(kConvert) / sizeof(kConvert[0]);
size_t nn;
for (nn = 0; nn < kConvertSize; ++nn) {
if (scancode == kConvert[nn].scancode) {
return kConvert[nn].keycode;
}
}
return -1;
}
static SkinKeyMod sdl_mod_to_key_mod(int sdl_mod) {
static const struct {
int sdl_flag;
int mod_flag;
} kConvert[] = {
{ KMOD_LSHIFT, kKeyModLShift },
{ KMOD_RSHIFT, kKeyModRShift },
{ KMOD_LCTRL, kKeyModLCtrl },
{ KMOD_RCTRL, kKeyModRCtrl },
{ KMOD_LALT, kKeyModLAlt },
{ KMOD_RALT, kKeyModRAlt },
{ KMOD_NUM, kKeyModNumLock },
};
const size_t kConvertSize = sizeof(kConvert) / sizeof(kConvert[0]);
SkinKeyMod mod = 0;
size_t nn;
for (nn = 0; nn < kConvertSize; ++nn) {
if (sdl_mod & kConvert[nn].sdl_flag) {
mod |= kConvert[nn].mod_flag;
}
}
return mod;
}
static int sdl_mouse_button_to_button(int sdl_button) {
switch (sdl_button) {
case 0: return 0;
case SDL_BUTTON_LEFT: return kMouseButtonLeft;
case SDL_BUTTON_RIGHT: return kMouseButtonRight;
case SDL_BUTTON_MIDDLE: return kMouseButtonCenter;
// scroll-wheel
case 4: return kMouseButtonScrollUp;
case 5: return kMouseButtonScrollDown;
default: return -1;
}
}
// Return true iff this event corresponds to the standard window system
// exit key combination (e.g. Alt-F4 on Windows, Command-Q on Apple).
static bool is_winsys_exit(SDL_Event* ev) {
#ifdef _WIN32
/* special code to deal with Alt-F4 properly */
return (ev->key.keysym.sym == SDL_SCANCODE_F4 && ev->key.keysym.mod & KMOD_ALT);
#elif defined(__APPLE__)
/* Not sure if Command-Q is used on all OS X locales. */
return (ev->key.keysym.sym == SDL_SCANCODE_Q && ev->key.keysym.mod & KMOD_GUI);
#else
return false;
#endif
}
bool skin_event_poll(SkinEvent* event) {
SDL_Event ev;
// SDL2 is very confused about the state of NumLock, it always assumes that
// it is not active when the program starts, then detects actual NumLock
// keypresses to toggle its internal state.
//
// Consider the following sequence of key presses:
//
// a) Keypad 7
// b) NumLock
// c) Keypad 7
//
// 1) If the program starts when NumLock is not active, the value of the
// KMOD_NUM bit flag is consistent with the use of the NumLock key,
// i.e. the sequence above generates:
//
// 1 KEYDOWN event for Keypad_7 (KMOD_NUM is _unset_)
// 1 KEYUP event for Keypad_7 (KMOD_NUM is _unset_)
//
// 1 KEYDOWN event for NUMLOCKCLEAR (with KMOD_NUM _unset_)
// 1 KEYUP event for NUMLOCKCLEAR (with KMOD_NUM set)
//
// 1 KEYDOWN event for Keypad_7 (KMOD_NUM is set)
// 1 TEXTINPUT event for '7'
// 1 KEYUP event for Keypad_7 (KMOD_NUM is set)
//
// 2) If the program starts when Numlock is active, things are
// unfortunately different:
//
// 1 KEYDOWN event for Keypad_7 (KMOD_NUM is _unset_)
// 1 TEXTINPUT event for '7'
// 1 KEYUP event for Keypad_7 (KMOD_NUM is _unset_)
//
// 1 KEYDOWN event for NUMLOCKCLEAR (KMOD_NUM is _unset_)
// 1 KEYUP event for NUMLOCKCLEAR (KMOD_NUM is set)
//
// 1 KEYDOWN event for Keypad_7 (KMOD_NUM is set)
// 1 KEYUP event for Keypad_7 (KMOD_NUM is set)
//
// Notice that in both cases, the first KEYDOWN are identical and have
// KMOD_NUM _unset_.
//
// To work around this problem, we need to detect situation 2, and "fix"
// The KMOD_NUM state.
static bool keypad_pressed_without_KMOD_NUM = false;
for (;;) {
if (!SDL_PollEvent(&ev)) {
return false;
}
switch (ev.type) {
case SDL_WINDOWEVENT:
if (ev.window.event == SDL_WINDOWEVENT_EXPOSED) {
event->type = kEventVideoExpose;
return true;
}
break;
case SDL_KEYDOWN:
if (is_winsys_exit(&ev)) {
event->type = kEventQuit;
return true;
}
keypad_pressed_without_KMOD_NUM =
sdl_is_keypad_keycode(ev.key.keysym.sym) &&
!(ev.key.keysym.mod & KMOD_NUM);
// fall-through.
case SDL_KEYUP: {
bool down = (ev.type == SDL_KEYDOWN);
event->type = down ? kEventKeyDown : kEventKeyUp;
int sdl_scancode = ev.key.keysym.scancode;
int sdl_sym = ev.key.keysym.sym;
int sdl_mod = ev.key.keysym.mod;
if (!sdl_is_special_keypair(sdl_sym, sdl_mod)) {
// Ignore this key event because a TEXTINPUT event
// will also be generated for it.
D("IGNORING KEY%s: sdl_sym=0x%x sdl_mod=0x%x keycode=0x%x scancode=0x%x mod=0x%x\n",
down ? "DOWN" : "UP",
sdl_sym,
sdl_scancode,
sdl_mod,
event->u.key.keycode,
event->u.key.mod);
break;
}
event->u.key.keycode = sdl_scancode_to_key_code(sdl_scancode);
event->u.key.mod = sdl_mod_to_key_mod(sdl_mod);
D("KEY%s: sdl_sym=0x%x sdl_mod=0x%x keycode=0x%x scancode=0x%x mod=0x%x\n",
down ? "DOWN" : "UP",
sdl_sym,
sdl_scancode,
sdl_mod,
event->u.key.keycode,
event->u.key.mod);
if (ev.type == SDL_KEYUP) {
keypad_pressed_without_KMOD_NUM = false;
}
return true;
}
case SDL_TEXTINPUT:
// Try to ignore keypad presses.
if (keypad_pressed_without_KMOD_NUM) {
// Need to fix KMOD_NUM state here.
SDL_Keymod mod = SDL_GetModState();
if (!(mod & KMOD_NUM)) {
mod |= KMOD_NUM;
SDL_SetModState(mod);
D("Detected NUMLOCK is active - toggling KMOD_NUM flag");
// Break here to ignore this keypress.
break;
}
}
event->type = kEventTextInput;
event->u.text.down = true;
snprintf((char*)event->u.text.text,
sizeof(event->u.text.text),
"%s",
ev.text.text);
D("TEXTINPUT text=[%s]\n", ev.text.text);
return true;
case SDL_MOUSEMOTION:
event->type = kEventMouseMotion;
event->u.mouse.x = ev.button.x;
event->u.mouse.y = ev.button.y;
event->u.mouse.xrel = ev.motion.xrel;
event->u.mouse.yrel = ev.motion.yrel;
event->u.mouse.button =
sdl_mouse_button_to_button(ev.button.button);
if (event->u.mouse.button < 0) {
break;
}
return true;
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
event->type = (ev.type == SDL_MOUSEBUTTONDOWN) ?
kEventMouseButtonDown :
kEventMouseButtonUp;
event->u.mouse.x = ev.button.x;
event->u.mouse.y = ev.button.y;
event->u.mouse.button =
sdl_mouse_button_to_button(ev.button.button);
if (event->u.mouse.button < 0) {
break;
}
return true;
case SDL_QUIT:
event->type = kEventQuit;
return true;
default:
// Ignore other events.
;
}
}
}
void skin_event_enable_unicode(bool enabled) {
// nothing to do here.
}