SFML/src/SFML/Window/Android/WindowImplAndroid.cpp

701 lines
24 KiB
C++

////////////////////////////////////////////////////////////
//
// SFML - Simple and Fast Multimedia Library
// Copyright (C) 2013 Jonathan De Wachter (dewachter.jonathan@gmail.com)
//
// This software is provided 'as-is', without any express or implied warranty.
// In no event will the authors be held liable for any damages arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it freely,
// subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented;
// you must not claim that you wrote the original software.
// If you use this software in a product, an acknowledgment
// in the product documentation would be appreciated but is not required.
//
// 2. Altered source versions must be plainly marked as such,
// and must not be misrepresented as being the original software.
//
// 3. This notice may not be removed or altered from any source distribution.
//
////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
// Headers
////////////////////////////////////////////////////////////
#include <SFML/Window/WindowStyle.hpp> // important to be included first (conflict with None)
#include <SFML/Window/Android/WindowImplAndroid.hpp>
#include <SFML/Window/Event.hpp>
#include <SFML/System/Lock.hpp>
#include <SFML/System/Err.hpp>
#include <android/looper.h>
// Define missing constants
#define AMOTION_EVENT_ACTION_HOVER_MOVE 0x00000007
#define AMOTION_EVENT_ACTION_SCROLL 0x00000008
////////////////////////////////////////////////////////////
// Private data
////////////////////////////////////////////////////////////
namespace sf
{
namespace priv
{
WindowImplAndroid* WindowImplAndroid::singleInstance = NULL;
////////////////////////////////////////////////////////////
WindowImplAndroid::WindowImplAndroid(WindowHandle handle)
: m_size(0, 0)
, m_windowBeingCreated(false)
, m_windowBeingDestroyed(false)
{
}
////////////////////////////////////////////////////////////
WindowImplAndroid::WindowImplAndroid(VideoMode mode, const String& title, unsigned long style, const ContextSettings& settings)
: m_size(mode.width, mode.height)
, m_windowBeingCreated(false)
, m_windowBeingDestroyed(false)
{
ActivityStates* states = getActivity(NULL);
Lock lock(states->mutex);
if (style& Style::Fullscreen)
states->fullscreen = true;
WindowImplAndroid::singleInstance = this;
states->forwardEvent = forwardEvent;
// Register process event callback
states->processEvent = processEvent;
states->initialized = true;
}
////////////////////////////////////////////////////////////
WindowImplAndroid::~WindowImplAndroid()
{
}
////////////////////////////////////////////////////////////
WindowHandle WindowImplAndroid::getSystemHandle() const
{
ActivityStates* states = getActivity(NULL);
Lock lock(states->mutex);
return states->window;
}
////////////////////////////////////////////////////////////
void WindowImplAndroid::processEvents()
{
// Process incoming OS events
ALooper_pollAll(0, NULL, NULL, NULL);
ActivityStates* states = getActivity(NULL);
sf::Lock lock(states->mutex);
if (m_windowBeingCreated)
{
states->context->createSurface(states->window);
m_windowBeingCreated = false;
}
if (m_windowBeingDestroyed)
{
states->context->destroySurface();
m_windowBeingDestroyed = false;
}
states->updated = true;
}
////////////////////////////////////////////////////////////
Vector2i WindowImplAndroid::getPosition() const
{
// Not applicable
return Vector2i(0, 0);
}
////////////////////////////////////////////////////////////
void WindowImplAndroid::setPosition(const Vector2i& position)
{
// Not applicable
}
////////////////////////////////////////////////////////////
Vector2u WindowImplAndroid::getSize() const
{
return m_size;
}
////////////////////////////////////////////////////////////
void WindowImplAndroid::setSize(const Vector2u& size)
{
}
////////////////////////////////////////////////////////////
void WindowImplAndroid::setTitle(const String& title)
{
// Not applicable
}
////////////////////////////////////////////////////////////
void WindowImplAndroid::setIcon(unsigned int width, unsigned int height, const Uint8* pixels)
{
// Not applicable
}
////////////////////////////////////////////////////////////
void WindowImplAndroid::setVisible(bool visible)
{
// Not applicable
}
////////////////////////////////////////////////////////////
void WindowImplAndroid::setMouseCursorVisible(bool visible)
{
// Not applicable
}
////////////////////////////////////////////////////////////
void WindowImplAndroid::setKeyRepeatEnabled(bool enabled)
{
// Not applicable
}
////////////////////////////////////////////////////////////
void WindowImplAndroid::requestFocus()
{
// Not applicable
}
////////////////////////////////////////////////////////////
bool WindowImplAndroid::hasFocus() const
{
// Not applicable
return false;
}
////////////////////////////////////////////////////////////
void WindowImplAndroid::forwardEvent(const Event& event)
{
ActivityStates* states = getActivity(NULL);
if (event.type == Event::GainedFocus)
{
WindowImplAndroid::singleInstance->m_size.x = ANativeWindow_getWidth(states->window);
WindowImplAndroid::singleInstance->m_size.y = ANativeWindow_getHeight(states->window);
WindowImplAndroid::singleInstance->m_windowBeingCreated = true;
}
else if (event.type == Event::LostFocus)
{
WindowImplAndroid::singleInstance->m_windowBeingDestroyed = true;
}
WindowImplAndroid::singleInstance->pushEvent(event);
}
////////////////////////////////////////////////////////////
int WindowImplAndroid::processEvent(int fd, int events, void* data)
{
ActivityStates* states = getActivity(NULL);
Lock lock(states->mutex);
AInputEvent* _event = NULL;
if (AInputQueue_getEvent(states->inputQueue, &_event) >= 0)
{
if (AInputQueue_preDispatchEvent(states->inputQueue, _event))
return 1;
int32_t handled = 0;
int32_t type = AInputEvent_getType(_event);
if (type == AINPUT_EVENT_TYPE_KEY)
{
int32_t action = AKeyEvent_getAction(_event);
int32_t key = AKeyEvent_getKeyCode(_event);
if ((action == AKEY_EVENT_ACTION_DOWN || action == AKEY_EVENT_ACTION_UP || action == AKEY_EVENT_ACTION_MULTIPLE) &&
key != AKEYCODE_VOLUME_UP && key != AKEYCODE_VOLUME_DOWN)
{
processKeyEvent(_event, states);
handled = 1;
}
}
else if (type == AINPUT_EVENT_TYPE_MOTION)
{
int32_t action = AMotionEvent_getAction(_event);
switch (action & AMOTION_EVENT_ACTION_MASK)
{
case AMOTION_EVENT_ACTION_SCROLL:
{
processScrollEvent(_event, states);
handled = 1;
break;
}
case AMOTION_EVENT_ACTION_HOVER_MOVE:
case AMOTION_EVENT_ACTION_MOVE:
{
processMotionEvent(_event, states);
handled = 1;
break;
}
// todo: investigate AMOTION_EVENT_OUTSIDE
case AMOTION_EVENT_ACTION_POINTER_DOWN:
case AMOTION_EVENT_ACTION_DOWN:
{
processPointerEvent(true, _event, states);
handled = 1;
break;
}
case AMOTION_EVENT_ACTION_POINTER_UP:
case AMOTION_EVENT_ACTION_UP:
case AMOTION_EVENT_ACTION_CANCEL:
{
processPointerEvent(false, _event, states);
handled = 1;
break;
}
}
}
AInputQueue_finishEvent(states->inputQueue, _event, handled);
}
return 1;
}
////////////////////////////////////////////////////////////
void WindowImplAndroid::processScrollEvent(AInputEvent* _event, ActivityStates* states)
{
// Prepare the java virtual machine
jint lResult;
jint lFlags = 0;
JavaVM* lJavaVM = states->activity->vm;
JNIEnv* lJNIEnv = states->activity->env;
JavaVMAttachArgs lJavaVMAttachArgs;
lJavaVMAttachArgs.version = JNI_VERSION_1_6;
lJavaVMAttachArgs.name = "NativeThread";
lJavaVMAttachArgs.group = NULL;
lResult=lJavaVM->AttachCurrentThread(&lJNIEnv, &lJavaVMAttachArgs);
if (lResult == JNI_ERR)
err() << "Failed to initialize JNI, couldn't get the unicode value" << std::endl;
// Retrieve everything we need to create this MotionEvent in java
jlong downTime = AMotionEvent_getDownTime(_event);
jlong eventTime = AMotionEvent_getEventTime(_event);
jint action = AMotionEvent_getAction(_event);
jfloat x = AMotionEvent_getX(_event, 0);
jfloat y = AMotionEvent_getY(_event, 0);
jfloat pressure = AMotionEvent_getPressure(_event, 0);
jfloat size = AMotionEvent_getSize(_event, 0);
jint metaState = AMotionEvent_getMetaState(_event);
jfloat xPrecision = AMotionEvent_getXPrecision(_event);
jfloat yPrecision = AMotionEvent_getYPrecision(_event);
jint deviceId = AInputEvent_getDeviceId(_event);
jint edgeFlags = AMotionEvent_getEdgeFlags(_event);
// Create the MotionEvent object in java trough its static constructor obtain()
jclass ClassMotionEvent = lJNIEnv->FindClass("android/view/MotionEvent");
jmethodID StaticMethodObtain = lJNIEnv->GetStaticMethodID(ClassMotionEvent, "obtain", "(JJIFFFFIFFII)Landroid/view/MotionEvent;");
jobject ObjectMotionEvent = lJNIEnv->CallStaticObjectMethod(ClassMotionEvent, StaticMethodObtain, downTime, eventTime, action, x, y, pressure, size, metaState, xPrecision, yPrecision, deviceId, edgeFlags);
// Call its getAxisValue() method to get the delta value of our wheel move event
jmethodID MethodGetAxisValue = lJNIEnv->GetMethodID(ClassMotionEvent, "getAxisValue", "(I)F");
jfloat delta = lJNIEnv->CallFloatMethod(ObjectMotionEvent, MethodGetAxisValue, 0x00000001);
// Create and send our mouse wheel event
Event event;
event.type = Event::MouseWheelMoved;
event.mouseWheel.delta = static_cast<double>(delta);
event.mouseWheel.x = AMotionEvent_getX(_event, 0);
event.mouseWheel.y = AMotionEvent_getY(_event, 0);
forwardEvent(event);
// Dettach this thread from the JVM
lJavaVM->DetachCurrentThread();
}
////////////////////////////////////////////////////////////
void WindowImplAndroid::processKeyEvent(AInputEvent* _event, ActivityStates* states)
{
int32_t device = AInputEvent_getSource(_event);
int32_t action = AKeyEvent_getAction(_event);
int32_t key = AKeyEvent_getKeyCode(_event);
int32_t metakey = AKeyEvent_getMetaState(_event);
Event event;
event.key.code = androidKeyToSF(key);
event.key.alt = metakey & AMETA_ALT_ON;
event.key.control = false;
event.key.shift = metakey & AMETA_SHIFT_ON;
switch (action)
{
case AKEY_EVENT_ACTION_DOWN:
event.type = Event::KeyPressed;
forwardEvent(event);
break;
case AKEY_EVENT_ACTION_UP:
event.type = Event::KeyReleased;
forwardEvent(event);
if (int unicode = getUnicode(_event))
{
event.type = Event::TextEntered;
event.text.unicode = unicode;
forwardEvent(event);
}
break;
case AKEY_EVENT_ACTION_MULTIPLE:
// Since complex inputs don't get separate key down/up events
// both have to be faked at once
event.type = Event::KeyPressed;
forwardEvent(event);
event.type = Event::KeyReleased;
forwardEvent(event);
// This requires some special treatment, since this might represent
// a repetition of key presses or a complete sequence
if (key == AKEYCODE_UNKNOWN)
{
// This is a unique sequence, which is not yet exposed in the NDK
// http://code.google.com/p/android/issues/detail?id=33998
}
else
{
// This is a repeated sequence
if (int unicode = getUnicode(_event))
{
event.type = Event::TextEntered;
event.text.unicode = unicode;
int32_t repeats = AKeyEvent_getRepeatCount(_event);
for (int32_t i = 0; i < repeats; ++i)
forwardEvent(event);
}
}
break;
}
}
////////////////////////////////////////////////////////////
void WindowImplAndroid::processMotionEvent(AInputEvent* _event, ActivityStates* states)
{
int32_t device = AInputEvent_getSource(_event);
int32_t action = AMotionEvent_getAction(_event);
Event event;
if (device == AINPUT_SOURCE_MOUSE)
event.type = Event::MouseMoved;
else if (device == AINPUT_SOURCE_TOUCHSCREEN)
event.type = Event::TouchMoved;
int pointerCount = AMotionEvent_getPointerCount(_event);
for (int p = 0; p < pointerCount; p++)
{
int id = AMotionEvent_getPointerId(_event, p);
float x = AMotionEvent_getX(_event, p);
float y = AMotionEvent_getY(_event, p);
if (device == AINPUT_SOURCE_MOUSE)
{
event.mouseMove.x = x;
event.mouseMove.y = y;
states->mousePosition = Vector2i(event.mouseMove.x, event.mouseMove.y);
}
else if (device == AINPUT_SOURCE_TOUCHSCREEN)
{
if (states->touchEvents[id].x == x && states->touchEvents[id].y == y)
continue;
event.touch.finger = id;
event.touch.x = x;
event.touch.y = y;
states->touchEvents[id] = Vector2i(event.touch.x, event.touch.y);
}
forwardEvent(event);
}
}
////////////////////////////////////////////////////////////
void WindowImplAndroid::processPointerEvent(bool isDown, AInputEvent* _event, ActivityStates* states)
{
int32_t device = AInputEvent_getSource(_event);
int32_t action = AMotionEvent_getAction(_event);
int index = (action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
int id = AMotionEvent_getPointerId(_event, index);
float x = AMotionEvent_getX(_event, index);
float y = AMotionEvent_getY(_event, index);
Event event;
if (isDown)
{
if (device == AINPUT_SOURCE_MOUSE)
{
event.type = Event::MouseButtonPressed;
event.mouseButton.button = static_cast<Mouse::Button>(id);
event.mouseButton.x = x;
event.mouseButton.y = y;
if (id >= 0 && id < Mouse::ButtonCount)
states->isButtonPressed[id] = true;
}
else if (device == AINPUT_SOURCE_TOUCHSCREEN)
{
event.type = Event::TouchBegan;
event.touch.finger = id;
event.touch.x = x;
event.touch.y = y;
states->touchEvents[id] = Vector2i(event.touch.x, event.touch.y);
}
}
else
{
if (device == AINPUT_SOURCE_MOUSE)
{
event.type = Event::MouseButtonReleased;
event.mouseButton.button = static_cast<Mouse::Button>(id);
event.mouseButton.x = x;
event.mouseButton.y = y;
if (id >= 0 && id < Mouse::ButtonCount)
states->isButtonPressed[id] = false;
}
else if (device == AINPUT_SOURCE_TOUCHSCREEN)
{
event.type = Event::TouchEnded;
event.touch.finger = id;
event.touch.x = x;
event.touch.y = y;
states->touchEvents.erase(id);
}
}
forwardEvent(event);
}
////////////////////////////////////////////////////////////
Keyboard::Key WindowImplAndroid::androidKeyToSF(int32_t key)
{
switch (key)
{
case AKEYCODE_UNKNOWN:
case AKEYCODE_SOFT_LEFT:
case AKEYCODE_SOFT_RIGHT:
case AKEYCODE_HOME: return Keyboard::Unknown;
case AKEYCODE_BACK: return Keyboard::Escape;
case AKEYCODE_CALL:
case AKEYCODE_ENDCALL: return Keyboard::Unknown;
case AKEYCODE_0: return Keyboard::Num0;
case AKEYCODE_1: return Keyboard::Num1;
case AKEYCODE_2: return Keyboard::Num2;
case AKEYCODE_3: return Keyboard::Num3;
case AKEYCODE_4: return Keyboard::Num4;
case AKEYCODE_5: return Keyboard::Num5;
case AKEYCODE_6: return Keyboard::Num6;
case AKEYCODE_7: return Keyboard::Num7;
case AKEYCODE_8: return Keyboard::Num8;
case AKEYCODE_9: return Keyboard::Num9;
case AKEYCODE_STAR:
case AKEYCODE_POUND:
case AKEYCODE_DPAD_UP:
case AKEYCODE_DPAD_DOWN:
case AKEYCODE_DPAD_LEFT:
case AKEYCODE_DPAD_RIGHT:
case AKEYCODE_DPAD_CENTER:
case AKEYCODE_VOLUME_UP:
case AKEYCODE_VOLUME_DOWN:
case AKEYCODE_POWER:
case AKEYCODE_CAMERA:
case AKEYCODE_CLEAR: return Keyboard::Unknown;
case AKEYCODE_A: return Keyboard::A;
case AKEYCODE_B: return Keyboard::B;
case AKEYCODE_C: return Keyboard::C;
case AKEYCODE_D: return Keyboard::D;
case AKEYCODE_E: return Keyboard::E;
case AKEYCODE_F: return Keyboard::F;
case AKEYCODE_G: return Keyboard::G;
case AKEYCODE_H: return Keyboard::H;
case AKEYCODE_I: return Keyboard::I;
case AKEYCODE_J: return Keyboard::J;
case AKEYCODE_K: return Keyboard::K;
case AKEYCODE_L: return Keyboard::L;
case AKEYCODE_M: return Keyboard::M;
case AKEYCODE_N: return Keyboard::N;
case AKEYCODE_O: return Keyboard::O;
case AKEYCODE_P: return Keyboard::P;
case AKEYCODE_Q: return Keyboard::Q;
case AKEYCODE_R: return Keyboard::R;
case AKEYCODE_S: return Keyboard::S;
case AKEYCODE_T: return Keyboard::T;
case AKEYCODE_U: return Keyboard::U;
case AKEYCODE_V: return Keyboard::V;
case AKEYCODE_W: return Keyboard::W;
case AKEYCODE_X: return Keyboard::X;
case AKEYCODE_Y: return Keyboard::Y;
case AKEYCODE_Z: return Keyboard::Z;
case AKEYCODE_COMMA: return Keyboard::Comma;
case AKEYCODE_PERIOD: return Keyboard::Period;
case AKEYCODE_ALT_LEFT: return Keyboard::LAlt;
case AKEYCODE_ALT_RIGHT: return Keyboard::RAlt;
case AKEYCODE_SHIFT_LEFT: return Keyboard::LShift;
case AKEYCODE_SHIFT_RIGHT: return Keyboard::RShift;
case AKEYCODE_TAB: return Keyboard::Tab;
case AKEYCODE_SPACE: return Keyboard::Space;
case AKEYCODE_SYM:
case AKEYCODE_EXPLORER:
case AKEYCODE_ENVELOPE: return Keyboard::Unknown;
case AKEYCODE_ENTER: return Keyboard::Return;
case AKEYCODE_DEL: return Keyboard::Delete;
case AKEYCODE_GRAVE: return Keyboard::Tilde;
case AKEYCODE_MINUS: return Keyboard::Subtract;
case AKEYCODE_EQUALS: return Keyboard::Equal;
case AKEYCODE_LEFT_BRACKET: return Keyboard::LBracket;
case AKEYCODE_RIGHT_BRACKET: return Keyboard::RBracket;
case AKEYCODE_BACKSLASH: return Keyboard::BackSlash;
case AKEYCODE_SEMICOLON: return Keyboard::SemiColon;
case AKEYCODE_APOSTROPHE: return Keyboard::Quote;
case AKEYCODE_SLASH: return Keyboard::Slash;
case AKEYCODE_AT:
case AKEYCODE_NUM:
case AKEYCODE_HEADSETHOOK:
case AKEYCODE_FOCUS: // *Camera* focus
case AKEYCODE_PLUS:
case AKEYCODE_MENU:
case AKEYCODE_NOTIFICATION:
case AKEYCODE_SEARCH:
case AKEYCODE_MEDIA_PLAY_PAUSE:
case AKEYCODE_MEDIA_STOP:
case AKEYCODE_MEDIA_NEXT:
case AKEYCODE_MEDIA_PREVIOUS:
case AKEYCODE_MEDIA_REWIND:
case AKEYCODE_MEDIA_FAST_FORWARD:
case AKEYCODE_MUTE: return Keyboard::Unknown;
case AKEYCODE_PAGE_UP: return Keyboard::PageUp;
case AKEYCODE_PAGE_DOWN: return Keyboard::PageDown;
case AKEYCODE_PICTSYMBOLS:
case AKEYCODE_SWITCH_CHARSET:
case AKEYCODE_BUTTON_A:
case AKEYCODE_BUTTON_B:
case AKEYCODE_BUTTON_C:
case AKEYCODE_BUTTON_X:
case AKEYCODE_BUTTON_Y:
case AKEYCODE_BUTTON_Z:
case AKEYCODE_BUTTON_L1:
case AKEYCODE_BUTTON_R1:
case AKEYCODE_BUTTON_L2:
case AKEYCODE_BUTTON_R2:
case AKEYCODE_BUTTON_THUMBL:
case AKEYCODE_BUTTON_THUMBR:
case AKEYCODE_BUTTON_START:
case AKEYCODE_BUTTON_SELECT:
case AKEYCODE_BUTTON_MODE: return Keyboard::Unknown;
}
}
////////////////////////////////////////////////////////////
int WindowImplAndroid::getUnicode(AInputEvent* event)
{
// Retrieve activity states
ActivityStates* states = getActivity(NULL);
Lock lock(states->mutex);
// Initializes JNI
jint lResult;
jint lFlags = 0;
JavaVM* lJavaVM = states->activity->vm;
JNIEnv* lJNIEnv = states->activity->env;
JavaVMAttachArgs lJavaVMAttachArgs;
lJavaVMAttachArgs.version = JNI_VERSION_1_6;
lJavaVMAttachArgs.name = "NativeThread";
lJavaVMAttachArgs.group = NULL;
lResult=lJavaVM->AttachCurrentThread(&lJNIEnv, &lJavaVMAttachArgs);
if (lResult == JNI_ERR)
err() << "Failed to initialize JNI, couldn't get the unicode value" << std::endl;
// Retrieve key data from the input event
jlong downTime = AKeyEvent_getDownTime(event);
jlong eventTime = AKeyEvent_getEventTime(event);
jint action = AKeyEvent_getAction(event);
jint code = AKeyEvent_getKeyCode(event);
jint repeat = AKeyEvent_getRepeatCount(event); // not sure!
jint metaState = AKeyEvent_getMetaState(event);
jint deviceId = AInputEvent_getDeviceId(event);
jint scancode = AKeyEvent_getScanCode(event);
jint flags = AKeyEvent_getFlags(event);
jint source = AInputEvent_getSource(event);
// Construct a KeyEvent object from the event data
jclass ClassKeyEvent = lJNIEnv->FindClass("android/view/KeyEvent");
jmethodID KeyEventConstructor = lJNIEnv->GetMethodID(ClassKeyEvent, "<init>", "(JJIIIIIIII)V");
jobject ObjectKeyEvent = lJNIEnv->NewObject(ClassKeyEvent, KeyEventConstructor, downTime, eventTime, action, code, repeat, metaState, deviceId, scancode, flags, source);
// Call its getUnicodeChar() method to get the unicode value
jmethodID MethodGetUnicode = lJNIEnv->GetMethodID(ClassKeyEvent, "getUnicodeChar", "(I)I");
int unicode = lJNIEnv->CallIntMethod(ObjectKeyEvent, MethodGetUnicode, metaState);
// Dettach this thread from the JVM
lJavaVM->DetachCurrentThread();
return unicode;
}
} // namespace priv
} // namespace sf