| // Copyright 2017 The Cobalt Authors. All Rights Reserved. |
| // |
| // 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. |
| |
| #include "starboard/android/shared/input_events_generator.h" |
| |
| #include <android/keycodes.h> |
| #include <jni.h> |
| #include <math.h> |
| #include <utility> |
| |
| #include "starboard/android/shared/application_android.h" |
| #include "starboard/android/shared/jni_env_ext.h" |
| #include "starboard/android/shared/jni_utils.h" |
| #include "starboard/key.h" |
| |
| namespace starboard { |
| namespace android { |
| namespace shared { |
| |
| using ::starboard::shared::starboard::Application; |
| typedef ::starboard::android::shared::InputEventsGenerator::Event Event; |
| typedef ::starboard::android::shared::InputEventsGenerator::Events Events; |
| |
| namespace { |
| |
| SbKeyLocation GameActivityKeyToSbKeyLocation(GameActivityKeyEvent* event) { |
| int32_t keycode = event->keyCode; |
| switch (keycode) { |
| case AKEYCODE_ALT_LEFT: |
| case AKEYCODE_CTRL_LEFT: |
| case AKEYCODE_META_LEFT: |
| case AKEYCODE_SHIFT_LEFT: |
| return kSbKeyLocationLeft; |
| case AKEYCODE_ALT_RIGHT: |
| case AKEYCODE_CTRL_RIGHT: |
| case AKEYCODE_META_RIGHT: |
| case AKEYCODE_SHIFT_RIGHT: |
| return kSbKeyLocationRight; |
| } |
| return kSbKeyLocationUnspecified; |
| } |
| |
| unsigned int GameActivityInputEventMetaStateToSbModifiers(unsigned int meta) { |
| unsigned int modifiers = kSbKeyModifiersNone; |
| if (meta & AMETA_ALT_ON) { |
| modifiers |= kSbKeyModifiersAlt; |
| } |
| if (meta & AMETA_CTRL_ON) { |
| modifiers |= kSbKeyModifiersCtrl; |
| } |
| if (meta & AMETA_META_ON) { |
| modifiers |= kSbKeyModifiersMeta; |
| } |
| if (meta & AMETA_SHIFT_ON) { |
| modifiers |= kSbKeyModifiersShift; |
| } |
| return modifiers; |
| } |
| |
| std::unique_ptr<Event> CreateMoveEventWithKey( |
| int32_t device_id, |
| SbWindow window, |
| SbKey key, |
| SbKeyLocation location, |
| const SbInputVector& input_vector) { |
| std::unique_ptr<SbInputData> data(new SbInputData()); |
| memset(data.get(), 0, sizeof(*data)); |
| |
| // window |
| data->window = window; |
| data->type = kSbInputEventTypeMove; |
| data->device_type = kSbInputDeviceTypeGamepad; |
| data->device_id = device_id; |
| |
| // key |
| data->key = key; |
| data->key_location = location; |
| data->key_modifiers = kSbKeyModifiersNone; |
| data->position = input_vector; |
| |
| return std::unique_ptr<Event>( |
| new Application::Event(kSbEventTypeInput, data.release(), |
| &Application::DeleteDestructor<SbInputData>)); |
| } |
| |
| float GetFlat(jobject input_device, int axis) { |
| if (input_device == NULL) { |
| return 0.0f; |
| } |
| |
| JniEnvExt* env = JniEnvExt::Get(); |
| ScopedLocalJavaRef<jobject> motion_range(env->CallObjectMethodOrAbort( |
| input_device, "getMotionRange", |
| "(I)Landroid/view/InputDevice$MotionRange;", axis)); |
| |
| if (motion_range.Get() == NULL) { |
| return 0.0f; |
| } |
| |
| float flat = |
| env->CallFloatMethodOrAbort(motion_range.Get(), "getFlat", "()F"); |
| |
| SB_DCHECK(flat < 1.0f); |
| return flat; |
| } |
| |
| bool IsDPadKey(SbKey key) { |
| return key == kSbKeyGamepadDPadUp || key == kSbKeyGamepadDPadDown || |
| key == kSbKeyGamepadDPadLeft || key == kSbKeyGamepadDPadRight; |
| } |
| |
| SbKey AInputEventToSbKey(GameActivityKeyEvent* event) { |
| auto keycode = event->keyCode; |
| switch (keycode) { |
| // Modifiers |
| case AKEYCODE_ALT_LEFT: |
| case AKEYCODE_ALT_RIGHT: |
| case AKEYCODE_MENU: |
| return kSbKeyMenu; |
| case AKEYCODE_CTRL_LEFT: |
| case AKEYCODE_CTRL_RIGHT: |
| return kSbKeyControl; |
| case AKEYCODE_META_LEFT: |
| return kSbKeyLwin; |
| case AKEYCODE_META_RIGHT: |
| return kSbKeyRwin; |
| case AKEYCODE_SHIFT_LEFT: |
| case AKEYCODE_SHIFT_RIGHT: |
| return kSbKeyShift; |
| case AKEYCODE_CAPS_LOCK: |
| return kSbKeyCapital; |
| case AKEYCODE_NUM_LOCK: |
| return kSbKeyNumlock; |
| case AKEYCODE_SCROLL_LOCK: |
| return kSbKeyScroll; |
| |
| // System functions |
| case AKEYCODE_SLEEP: |
| return kSbKeySleep; |
| case AKEYCODE_HELP: |
| return kSbKeyHelp; |
| |
| // Navigation |
| case AKEYCODE_BACK: // Android back button, not backspace |
| case AKEYCODE_ESCAPE: |
| return kSbKeyEscape; |
| |
| // Enter/Select |
| case AKEYCODE_ENTER: |
| case AKEYCODE_NUMPAD_ENTER: |
| return kSbKeyReturn; |
| |
| // Focus movement |
| case AKEYCODE_PAGE_UP: |
| return kSbKeyPrior; |
| case AKEYCODE_PAGE_DOWN: |
| return kSbKeyNext; |
| case AKEYCODE_MOVE_HOME: |
| return kSbKeyHome; |
| case AKEYCODE_MOVE_END: |
| return kSbKeyEnd; |
| case AKEYCODE_SEARCH: |
| return kSbKeyBrowserSearch; |
| |
| // D-pad |
| case AKEYCODE_DPAD_UP: |
| return kSbKeyGamepadDPadUp; |
| case AKEYCODE_DPAD_DOWN: |
| return kSbKeyGamepadDPadDown; |
| case AKEYCODE_DPAD_LEFT: |
| return kSbKeyGamepadDPadLeft; |
| case AKEYCODE_DPAD_RIGHT: |
| return kSbKeyGamepadDPadRight; |
| case AKEYCODE_DPAD_CENTER: |
| return kSbKeyGamepad1; |
| |
| // Game controller |
| case AKEYCODE_BUTTON_A: |
| return kSbKeyGamepad1; |
| case AKEYCODE_BUTTON_B: |
| return kSbKeyGamepad2; |
| case AKEYCODE_BUTTON_C: |
| return kSbKeyUnknown; |
| case AKEYCODE_BUTTON_X: |
| return kSbKeyGamepad3; |
| case AKEYCODE_BUTTON_Y: |
| return kSbKeyGamepad4; |
| case AKEYCODE_BUTTON_L1: |
| return kSbKeyGamepadLeftBumper; |
| case AKEYCODE_BUTTON_R1: |
| return kSbKeyGamepadRightBumper; |
| case AKEYCODE_BUTTON_L2: |
| return kSbKeyGamepadLeftTrigger; |
| case AKEYCODE_BUTTON_R2: |
| return kSbKeyGamepadRightTrigger; |
| case AKEYCODE_BUTTON_THUMBL: |
| return kSbKeyGamepadLeftStick; |
| case AKEYCODE_BUTTON_THUMBR: |
| return kSbKeyGamepadRightStick; |
| case AKEYCODE_BUTTON_START: |
| return kSbKeyGamepad6; |
| case AKEYCODE_BUTTON_SELECT: |
| return kSbKeyGamepad5; |
| case AKEYCODE_BUTTON_MODE: |
| return kSbKeyModechange; |
| |
| // Media transport |
| case AKEYCODE_MEDIA_PLAY_PAUSE: |
| return kSbKeyMediaPlayPause; |
| case AKEYCODE_MEDIA_PLAY: |
| return kSbKeyPlay; |
| case AKEYCODE_MEDIA_PAUSE: |
| return kSbKeyPause; |
| case AKEYCODE_MEDIA_STOP: |
| return kSbKeyMediaStop; |
| case AKEYCODE_MEDIA_NEXT: |
| return kSbKeyMediaNextTrack; |
| case AKEYCODE_MEDIA_PREVIOUS: |
| return kSbKeyMediaPrevTrack; |
| case AKEYCODE_MEDIA_REWIND: |
| return kSbKeyMediaRewind; |
| case AKEYCODE_MEDIA_FAST_FORWARD: |
| return kSbKeyMediaFastForward; |
| #if SB_API_VERSION >= 15 |
| case AKEYCODE_MEDIA_RECORD: |
| return kSbKeyRecord; |
| #endif |
| |
| // TV Remote specific |
| case AKEYCODE_CHANNEL_UP: |
| return kSbKeyChannelUp; |
| case AKEYCODE_CHANNEL_DOWN: |
| return kSbKeyChannelDown; |
| case AKEYCODE_CAPTIONS: |
| return kSbKeyClosedCaption; |
| case AKEYCODE_INFO: |
| return kSbKeyInfo; |
| case AKEYCODE_GUIDE: |
| return kSbKeyGuide; |
| case AKEYCODE_LAST_CHANNEL: |
| return kSbKeyLast; |
| case AKEYCODE_MEDIA_AUDIO_TRACK: |
| return kSbKeyMediaAudioTrack; |
| |
| case AKEYCODE_PROG_RED: |
| return kSbKeyRed; |
| case AKEYCODE_PROG_GREEN: |
| return kSbKeyGreen; |
| case AKEYCODE_PROG_YELLOW: |
| return kSbKeyYellow; |
| case AKEYCODE_PROG_BLUE: |
| return kSbKeyBlue; |
| |
| // Whitespace |
| case AKEYCODE_TAB: |
| return kSbKeyTab; |
| case AKEYCODE_SPACE: |
| return kSbKeySpace; |
| |
| // Deletion |
| case AKEYCODE_DEL: // Backspace |
| return kSbKeyBack; |
| case AKEYCODE_FORWARD_DEL: |
| return kSbKeyDelete; |
| case AKEYCODE_CLEAR: |
| return kSbKeyClear; |
| |
| // Insert |
| case AKEYCODE_INSERT: |
| return kSbKeyInsert; |
| |
| // Symbols |
| case AKEYCODE_NUMPAD_ADD: |
| return kSbKeyAdd; |
| case AKEYCODE_PLUS: |
| case AKEYCODE_EQUALS: |
| case AKEYCODE_NUMPAD_EQUALS: |
| return kSbKeyOemPlus; |
| case AKEYCODE_NUMPAD_SUBTRACT: |
| return kSbKeySubtract; |
| case AKEYCODE_MINUS: |
| return kSbKeyOemMinus; |
| case AKEYCODE_NUMPAD_MULTIPLY: |
| return kSbKeyMultiply; |
| case AKEYCODE_NUMPAD_DIVIDE: |
| return kSbKeyDivide; |
| case AKEYCODE_COMMA: |
| case AKEYCODE_NUMPAD_COMMA: |
| return kSbKeyOemComma; |
| case AKEYCODE_NUMPAD_DOT: |
| return kSbKeyDecimal; |
| case AKEYCODE_PERIOD: |
| return kSbKeyOemPeriod; |
| case AKEYCODE_SEMICOLON: |
| return kSbKeyOem1; |
| case AKEYCODE_SLASH: |
| return kSbKeyOem2; |
| case AKEYCODE_GRAVE: |
| return kSbKeyOem3; |
| case AKEYCODE_LEFT_BRACKET: |
| return kSbKeyOem4; |
| case AKEYCODE_BACKSLASH: |
| return kSbKeyOem5; |
| case AKEYCODE_RIGHT_BRACKET: |
| return kSbKeyOem6; |
| case AKEYCODE_APOSTROPHE: |
| return kSbKeyOem7; |
| |
| // Function keys |
| case AKEYCODE_F1: |
| case AKEYCODE_F2: |
| case AKEYCODE_F3: |
| case AKEYCODE_F4: |
| case AKEYCODE_F5: |
| case AKEYCODE_F6: |
| case AKEYCODE_F7: |
| case AKEYCODE_F8: |
| case AKEYCODE_F9: |
| case AKEYCODE_F10: |
| case AKEYCODE_F11: |
| case AKEYCODE_F12: |
| return static_cast<SbKey>(kSbKeyF1 + (keycode - AKEYCODE_F1)); |
| |
| // Digits |
| case AKEYCODE_0: |
| case AKEYCODE_1: |
| case AKEYCODE_2: |
| case AKEYCODE_3: |
| case AKEYCODE_4: |
| case AKEYCODE_5: |
| case AKEYCODE_6: |
| case AKEYCODE_7: |
| case AKEYCODE_8: |
| case AKEYCODE_9: |
| return static_cast<SbKey>(kSbKey0 + (keycode - AKEYCODE_0)); |
| |
| // Numpad digits |
| case AKEYCODE_NUMPAD_0: |
| case AKEYCODE_NUMPAD_1: |
| case AKEYCODE_NUMPAD_2: |
| case AKEYCODE_NUMPAD_3: |
| case AKEYCODE_NUMPAD_4: |
| case AKEYCODE_NUMPAD_5: |
| case AKEYCODE_NUMPAD_6: |
| case AKEYCODE_NUMPAD_7: |
| case AKEYCODE_NUMPAD_8: |
| case AKEYCODE_NUMPAD_9: |
| return static_cast<SbKey>(kSbKeyNumpad0 + (keycode - AKEYCODE_NUMPAD_0)); |
| |
| // Alphabetic |
| case AKEYCODE_A: |
| case AKEYCODE_B: |
| case AKEYCODE_C: |
| case AKEYCODE_D: |
| case AKEYCODE_E: |
| case AKEYCODE_F: |
| case AKEYCODE_G: |
| case AKEYCODE_H: |
| case AKEYCODE_I: |
| case AKEYCODE_J: |
| case AKEYCODE_K: |
| case AKEYCODE_L: |
| case AKEYCODE_M: |
| case AKEYCODE_N: |
| case AKEYCODE_O: |
| case AKEYCODE_P: |
| case AKEYCODE_Q: |
| case AKEYCODE_R: |
| case AKEYCODE_S: |
| case AKEYCODE_T: |
| case AKEYCODE_U: |
| case AKEYCODE_V: |
| case AKEYCODE_W: |
| case AKEYCODE_X: |
| case AKEYCODE_Y: |
| case AKEYCODE_Z: |
| return static_cast<SbKey>(kSbKeyA + (keycode - AKEYCODE_A)); |
| |
| // Required, but will not be used by the YouTube application. |
| case AKEYCODE_VOLUME_UP: |
| return kSbKeyVolumeUp; |
| case AKEYCODE_VOLUME_DOWN: |
| return kSbKeyVolumeDown; |
| case AKEYCODE_MUTE: |
| return kSbKeyVolumeMute; |
| |
| // Don't handle these keys so the OS can in a uniform manner. |
| case AKEYCODE_BRIGHTNESS_UP: |
| case AKEYCODE_BRIGHTNESS_DOWN: |
| default: |
| return kSbKeyUnknown; |
| } |
| } |
| |
| } // namespace |
| |
| InputEventsGenerator::InputEventsGenerator(SbWindow window) |
| : window_(window), |
| hat_value_(), |
| left_thumbstick_key_pressed_{kSbKeyUnknown, kSbKeyUnknown} { |
| SB_DCHECK(SbWindowIsValid(window_)); |
| } |
| |
| InputEventsGenerator::~InputEventsGenerator() {} |
| |
| // For a left joystick, AMOTION_EVENT_AXIS_X reports the absolute X position of |
| // the joystick. The value is normalized to a range from -1.0 (left) to 1.0 |
| // (right). |
| // |
| // For a left joystick, AMOTION_EVENT_AXIS_Y reports the absolute Y position of |
| // the joystick. The value is normalized to a range from -1.0 (up or far) to 1.0 |
| // (down or near). |
| // |
| // On game pads with two analog joysticks, AMOTION_EVENT_AXIS_Z is often |
| // reinterpreted to report the absolute X position of the second joystick. |
| // |
| // On game pads with two analog joysticks, AMOTION_EVENT_AXIS_RZ is often |
| // reinterpreted to report the absolute Y position of the second joystick. |
| void InputEventsGenerator::ProcessJoyStickEvent( |
| FlatAxis axis, |
| int32_t motion_axis, |
| GameActivityMotionEvent* android_motion_event, |
| Events* events) { |
| SB_DCHECK(android_motion_event->pointerCount > 0); |
| |
| int32_t device_id = android_motion_event->deviceId; |
| SB_DCHECK(device_flat_.find(device_id) != device_flat_.end()); |
| |
| float flat = device_flat_[device_id][axis]; |
| float offset = GameActivityPointerAxes_getAxisValue( |
| &android_motion_event->pointers[0], motion_axis); |
| int sign = offset < 0.0f ? -1 : 1; |
| |
| if (fabs(offset) < flat) { |
| offset = sign * flat; |
| } |
| // Rescaled the range: |
| // [-1.0f, -flat] to [-1.0f, 0.0f] and [flat, 1.0f] to [0.0f, 1.0f] |
| offset = (offset - sign * flat) / (1 - flat); |
| |
| // Report up and left as negative values. |
| SbInputVector input_vector; |
| SbKey key = kSbKeyUnknown; |
| SbKeyLocation location = kSbKeyLocationUnspecified; |
| switch (axis) { |
| case kLeftX: { |
| input_vector.x = offset; |
| input_vector.y = 0.0f; |
| key = kSbKeyGamepadLeftStickLeft; |
| location = kSbKeyLocationLeft; |
| break; |
| } |
| case kLeftY: { |
| input_vector.x = 0.0f; |
| input_vector.y = offset; |
| key = kSbKeyGamepadLeftStickUp; |
| location = kSbKeyLocationLeft; |
| break; |
| } |
| case kRightX: { |
| input_vector.x = offset; |
| input_vector.y = 0.0f; |
| key = kSbKeyGamepadRightStickLeft; |
| location = kSbKeyLocationRight; |
| break; |
| } |
| case kRightY: { |
| input_vector.x = 0.0f; |
| input_vector.y = offset; |
| key = kSbKeyGamepadRightStickUp; |
| location = kSbKeyLocationRight; |
| break; |
| } |
| default: |
| SB_NOTREACHED(); |
| } |
| |
| events->push_back( |
| CreateMoveEventWithKey(device_id, window_, key, location, input_vector)); |
| } |
| |
| namespace { |
| |
| // Generate a Starboard event from an Android motion event, with the SbKey and |
| // SbInputEventType pre-specified (so that it can be used by event |
| // synthesization as well.) |
| void PushKeyEvent(SbKey key, |
| SbInputEventType type, |
| SbWindow window, |
| GameActivityMotionEvent* android_event, |
| Events* events) { |
| std::unique_ptr<SbInputData> data(new SbInputData()); |
| memset(data.get(), 0, sizeof(*data)); |
| |
| // window |
| data->window = window; |
| data->type = type; |
| |
| // device |
| // TODO: differentiate gamepad, remote, etc. |
| data->device_type = kSbInputDeviceTypeKeyboard; |
| data->device_id = android_event->deviceId; |
| |
| data->key = key; |
| data->key_location = kSbKeyLocationUnspecified; |
| data->key_modifiers = |
| GameActivityInputEventMetaStateToSbModifiers(android_event->metaState); |
| |
| std::unique_ptr<Event> event( |
| new Event(kSbEventTypeInput, data.release(), |
| &Application::DeleteDestructor<SbInputData>)); |
| events->push_back(std::move(event)); |
| } |
| |
| // Generate a Starboard event from an Android key event, with the SbKey and |
| // SbInputEventType pre-specified (so that it can be used by event |
| // synthesization as well.) |
| void PushKeyEvent(SbKey key, |
| SbInputEventType type, |
| SbWindow window, |
| GameActivityKeyEvent* android_event, |
| Events* events) { |
| std::unique_ptr<SbInputData> data(new SbInputData()); |
| memset(data.get(), 0, sizeof(*data)); |
| |
| // window |
| data->window = window; |
| data->type = type; |
| |
| // device |
| // TODO: differentiate gamepad, remote, etc. |
| data->device_type = kSbInputDeviceTypeKeyboard; |
| data->device_id = android_event->deviceId; |
| |
| // key |
| data->key = key; |
| data->key_location = GameActivityKeyToSbKeyLocation(android_event); |
| data->key_modifiers = |
| GameActivityInputEventMetaStateToSbModifiers(android_event->metaState); |
| |
| std::unique_ptr<Event> event( |
| new Event(kSbEventTypeInput, data.release(), |
| &Application::DeleteDestructor<SbInputData>)); |
| events->push_back(std::move(event)); |
| } |
| |
| // Some helper enumerations to index into the InputEventsGenerator::hat_value_ |
| // array. |
| enum HatAxis { |
| kHatX, |
| kHatY, |
| }; |
| |
| struct HatValue { |
| HatAxis axis; |
| float value; |
| }; |
| |
| // Converts Starboard DPad direction keys to Starboard left thumbstick |
| // direction keys. |
| SbKey ConvertDPadKeyToThumbstickKey(SbKey key) { |
| switch (key) { |
| case kSbKeyGamepadDPadUp: |
| return kSbKeyGamepadLeftStickUp; |
| case kSbKeyGamepadDPadDown: |
| return kSbKeyGamepadLeftStickDown; |
| case kSbKeyGamepadDPadLeft: |
| return kSbKeyGamepadLeftStickLeft; |
| case kSbKeyGamepadDPadRight: |
| return kSbKeyGamepadLeftStickRight; |
| default: { |
| SB_NOTREACHED(); |
| return kSbKeyUnknown; |
| } |
| } |
| } |
| |
| // Convert a Starboard DPad direction key to a (axis, direction) pair. |
| HatValue HatValueForDPadKey(SbKey key) { |
| SB_DCHECK(IsDPadKey(key)); |
| |
| switch (key) { |
| case kSbKeyGamepadDPadUp: |
| return HatValue({kHatY, -1.0f}); |
| case kSbKeyGamepadDPadDown: |
| return HatValue({kHatY, 1.0f}); |
| case kSbKeyGamepadDPadLeft: |
| return HatValue({kHatX, -1.0f}); |
| case kSbKeyGamepadDPadRight: |
| return HatValue({kHatX, 1.0f}); |
| default: { |
| SB_NOTREACHED(); |
| return HatValue({kHatX, 0.0f}); |
| } |
| } |
| } |
| |
| // The inverse of HatValueForDPadKey(). |
| SbKey KeyForHatValue(const HatValue& hat_value) { |
| SB_DCHECK(hat_value.value > 0.5f || hat_value.value < -0.5f); |
| if (hat_value.axis == kHatX) { |
| if (hat_value.value > 0.5f) { |
| return kSbKeyGamepadDPadRight; |
| } else { |
| return kSbKeyGamepadDPadLeft; |
| } |
| } else if (hat_value.axis == kHatY) { |
| if (hat_value.value > 0.5f) { |
| return kSbKeyGamepadDPadDown; |
| } else { |
| return kSbKeyGamepadDPadUp; |
| } |
| } else { |
| SB_NOTREACHED(); |
| return kSbKeyUnknown; |
| } |
| } |
| |
| // Analyzes old axis values and new axis values and fire off any synthesized |
| // key press/unpress events as necessary. |
| void PossiblySynthesizeHatKeyEvents(HatAxis axis, |
| float old_value, |
| float new_value, |
| SbWindow window, |
| GameActivityMotionEvent* android_event, |
| Events* events) { |
| if (old_value == new_value) { |
| // No events to generate if the hat motion value did not change. |
| return; |
| } |
| |
| if (old_value > 0.5f || old_value < -0.5f) { |
| PushKeyEvent(KeyForHatValue(HatValue({axis, old_value})), |
| kSbInputEventTypeUnpress, window, android_event, events); |
| } |
| if (new_value > 0.5f || new_value < -0.5f) { |
| PushKeyEvent(KeyForHatValue(HatValue({axis, new_value})), |
| kSbInputEventTypePress, window, android_event, events); |
| } |
| } |
| |
| } // namespace |
| |
| bool InputEventsGenerator::CreateInputEventsFromGameActivityEvent( |
| GameActivityKeyEvent* android_event, |
| Events* events) { |
| SbInputEventType type; |
| switch (android_event->action) { |
| case AKEY_EVENT_ACTION_DOWN: |
| type = kSbInputEventTypePress; |
| break; |
| case AKEY_EVENT_ACTION_UP: |
| type = kSbInputEventTypeUnpress; |
| break; |
| default: |
| // TODO: send multiple events for AKEY_EVENT_ACTION_MULTIPLE |
| return false; |
| } |
| |
| SbKey key = AInputEventToSbKey(android_event); |
| |
| if (android_event->flags & AKEY_EVENT_FLAG_FALLBACK && IsDPadKey(key)) { |
| // For fallback DPad keys, we flow into special processing to manage the |
| // differentiation between the actual DPad and the left thumbstick, since |
| // Android conflates the key down/up events for these inputs. |
| ProcessFallbackDPadEvent(type, key, android_event, events); |
| } else { |
| PushKeyEvent(key, type, window_, android_event, events); |
| } |
| |
| return true; |
| } |
| |
| namespace { |
| |
| SbKey ButtonStateToSbKey(int32_t button_state) { |
| if (button_state & AMOTION_EVENT_BUTTON_PRIMARY) { |
| return kSbKeyMouse1; |
| } else if (button_state & AMOTION_EVENT_BUTTON_SECONDARY) { |
| return kSbKeyMouse2; |
| } else if (button_state & AMOTION_EVENT_BUTTON_TERTIARY) { |
| return kSbKeyMouse3; |
| } else if (button_state & AMOTION_EVENT_BUTTON_BACK) { |
| return kSbKeyBrowserBack; |
| } else if (button_state & AMOTION_EVENT_BUTTON_FORWARD) { |
| return kSbKeyBrowserForward; |
| } |
| return kSbKeyUnknown; |
| } |
| |
| // Get an SbKeyModifiers from a button state |
| unsigned int ButtonStateToSbModifiers(unsigned int button_state) { |
| unsigned int key_modifiers = kSbKeyModifiersNone; |
| if (button_state & AMOTION_EVENT_BUTTON_PRIMARY) { |
| key_modifiers |= kSbKeyModifiersPointerButtonLeft; |
| } |
| if (button_state & AMOTION_EVENT_BUTTON_SECONDARY) { |
| key_modifiers |= kSbKeyModifiersPointerButtonMiddle; |
| } |
| if (button_state & AMOTION_EVENT_BUTTON_TERTIARY) { |
| key_modifiers |= kSbKeyModifiersPointerButtonRight; |
| } |
| if (button_state & AMOTION_EVENT_BUTTON_BACK) { |
| key_modifiers |= kSbKeyModifiersPointerButtonBack; |
| } |
| if (button_state & AMOTION_EVENT_BUTTON_FORWARD) { |
| key_modifiers |= kSbKeyModifiersPointerButtonForward; |
| } |
| return key_modifiers; |
| } |
| |
| } // namespace |
| |
| bool InputEventsGenerator::ProcessPointerEvent( |
| GameActivityMotionEvent* android_event, |
| Events* events) { |
| float offset_x = GameActivityPointerAxes_getAxisValue( |
| &android_event->pointers[0], AMOTION_EVENT_AXIS_X); |
| float offset_y = GameActivityPointerAxes_getAxisValue( |
| &android_event->pointers[0], AMOTION_EVENT_AXIS_Y); |
| |
| std::unique_ptr<SbInputData> data(new SbInputData()); |
| memset(data.get(), 0, sizeof(*data)); |
| |
| data->window = window_; |
| SB_DCHECK(SbWindowIsValid(data->window)); |
| data->pressure = NAN; |
| data->size = {NAN, NAN}; |
| data->tilt = {NAN, NAN}; |
| unsigned int button_state = android_event->buttonState; |
| unsigned int button_modifiers = ButtonStateToSbModifiers(button_state); |
| |
| // Default to reporting pointer events as mouse events. |
| data->device_type = kSbInputDeviceTypeMouse; |
| |
| // Report both stylus and touchscreen events as touchscreen device events. |
| int32_t event_source = android_event->source; |
| if (((event_source & AINPUT_SOURCE_TOUCHSCREEN) != 0) || |
| ((event_source & AINPUT_SOURCE_STYLUS) != 0)) { |
| data->device_type = kSbInputDeviceTypeTouchScreen; |
| } |
| |
| data->device_id = android_event->deviceId; |
| data->key_modifiers = |
| button_modifiers | |
| GameActivityInputEventMetaStateToSbModifiers(android_event->metaState); |
| data->position.x = offset_x; |
| data->position.y = offset_y; |
| data->key = ButtonStateToSbKey(button_state); |
| |
| switch (android_event->action & AMOTION_EVENT_ACTION_MASK) { |
| case AMOTION_EVENT_ACTION_UP: |
| data->type = kSbInputEventTypeUnpress; |
| break; |
| case AMOTION_EVENT_ACTION_DOWN: |
| data->type = kSbInputEventTypePress; |
| break; |
| case AMOTION_EVENT_ACTION_MOVE: |
| case AMOTION_EVENT_ACTION_HOVER_MOVE: |
| data->type = kSbInputEventTypeMove; |
| break; |
| case AMOTION_EVENT_ACTION_SCROLL: { |
| float hscroll = GameActivityPointerAxes_getAxisValue( |
| &android_event->pointers[0], |
| AMOTION_EVENT_AXIS_HSCROLL); // left is -1 |
| float vscroll = GameActivityPointerAxes_getAxisValue( |
| &android_event->pointers[0], |
| AMOTION_EVENT_AXIS_VSCROLL); // down is -1 |
| float wheel = GameActivityPointerAxes_getAxisValue( |
| &android_event->pointers[0], |
| AMOTION_EVENT_AXIS_WHEEL); // this is not used |
| data->type = kSbInputEventTypeWheel; |
| data->key = kSbKeyUnknown; |
| data->delta.y = -vscroll; |
| data->delta.x = hscroll; |
| break; |
| } |
| default: |
| return false; |
| } |
| |
| events->push_back(std::unique_ptr<Event>( |
| new Application::Event(kSbEventTypeInput, data.release(), |
| &Application::DeleteDestructor<SbInputData>))); |
| return true; |
| } |
| |
| jobject GetDevice(GameActivityMotionEvent* android_motion_event) { |
| int32_t device_id = android_motion_event->deviceId; |
| JniEnvExt* env = JniEnvExt::Get(); |
| return env->CallStaticObjectMethodOrAbort( |
| "android/view/InputDevice", "getDevice", "(I)Landroid/view/InputDevice;", |
| device_id); |
| } |
| |
| bool InputEventsGenerator::CreateInputEventsFromGameActivityEvent( |
| GameActivityMotionEvent* android_event, |
| Events* events) { |
| if ((android_event->source & AINPUT_SOURCE_CLASS_POINTER) != 0) { |
| return ProcessPointerEvent(android_event, events); |
| } |
| |
| if ((android_event->source & AINPUT_SOURCE_JOYSTICK) == 0) { |
| // Only handles joystick events in the code below. |
| return false; |
| } |
| |
| ScopedLocalJavaRef<jobject> device(GetDevice(android_event)); |
| if (device.Get() != NULL) { |
| UpdateDeviceFlatMapIfNecessary(android_event, device.Get()); |
| ProcessJoyStickEvent(kLeftX, AMOTION_EVENT_AXIS_X, android_event, events); |
| ProcessJoyStickEvent(kLeftY, AMOTION_EVENT_AXIS_Y, android_event, events); |
| ProcessJoyStickEvent(kRightX, AMOTION_EVENT_AXIS_Z, android_event, events); |
| ProcessJoyStickEvent(kRightY, AMOTION_EVENT_AXIS_RZ, android_event, events); |
| |
| // Remember the "hat" input values (dpad on the game controller) to help |
| // differentiate hat vs. stick fallback events. |
| UpdateHatValuesAndPossiblySynthesizeKeyEvents(android_event, events); |
| } |
| |
| // Lie to Android and tell it that we did not process the motion event, |
| // causing Android to synthesize dpad key events for us. When we handle |
| // those synthesized key events we'll enqueue kSbKeyGamepadLeft rather |
| // than kSbKeyGamepadDPad events if they're from the joystick. |
| return false; |
| } |
| |
| // Special processing to disambiguate between DPad events and left-thumbstick |
| // direction key events. |
| void InputEventsGenerator::ProcessFallbackDPadEvent( |
| SbInputEventType type, |
| SbKey key, |
| GameActivityKeyEvent* android_event, |
| Events* events) { |
| SB_DCHECK(android_event->flags & AKEY_EVENT_FLAG_FALLBACK); |
| SB_DCHECK(IsDPadKey(key)); |
| |
| HatAxis hat_axis = HatValueForDPadKey(key).axis; |
| |
| if (hat_value_[hat_axis] != 0.0f && type == kSbInputEventTypePress) { |
| // Direction pad events are all assumed to be coming from the hat controls |
| // if motion events for that hat DPAD is active, but we do still handle |
| // repeat keys here. |
| if ((android_event->repeatCount) > 0) { |
| SB_LOG(INFO) << android_event->repeatCount; |
| PushKeyEvent(key, kSbInputEventTypePress, window_, android_event, events); |
| } |
| return; |
| } |
| |
| // If we get this far, then we are exclusively dealing with thumbstick events, |
| // as actual DPad events are processed in motion events by checking the |
| // hat axis representing the DPad. |
| SbKey thumbstick_key = ConvertDPadKeyToThumbstickKey(key); |
| |
| if (left_thumbstick_key_pressed_[hat_axis] != kSbKeyUnknown && |
| (type == kSbInputEventTypeUnpress || |
| left_thumbstick_key_pressed_[hat_axis] != thumbstick_key)) { |
| // Fire an unpressed event if our current key differs from the last seen |
| // key. |
| PushKeyEvent(left_thumbstick_key_pressed_[hat_axis], |
| kSbInputEventTypeUnpress, window_, android_event, events); |
| } |
| |
| if (type == kSbInputEventTypePress) { |
| PushKeyEvent(thumbstick_key, kSbInputEventTypePress, window_, android_event, |
| events); |
| left_thumbstick_key_pressed_[hat_axis] = thumbstick_key; |
| } else if (type == kSbInputEventTypeUnpress) { |
| left_thumbstick_key_pressed_[hat_axis] = kSbKeyUnknown; |
| } else { |
| SB_NOTREACHED(); |
| } |
| } |
| |
| // Update |InputEventsGenerator::hat_value_| according to the incoming motion |
| // event's data. Possibly generate DPad events based on any changes in value |
| // here. |
| void InputEventsGenerator::UpdateHatValuesAndPossiblySynthesizeKeyEvents( |
| GameActivityMotionEvent* android_motion_event, |
| Events* events) { |
| float new_hat_x = GameActivityPointerAxes_getAxisValue( |
| &android_motion_event->pointers[0], AMOTION_EVENT_AXIS_HAT_X); |
| PossiblySynthesizeHatKeyEvents(kHatX, hat_value_[kHatX], new_hat_x, window_, |
| android_motion_event, events); |
| hat_value_[kHatX] = new_hat_x; |
| |
| float new_hat_y = GameActivityPointerAxes_getAxisValue( |
| &android_motion_event->pointers[0], AMOTION_EVENT_AXIS_HAT_Y); |
| PossiblySynthesizeHatKeyEvents(kHatY, hat_value_[kHatY], new_hat_y, window_, |
| android_motion_event, events); |
| hat_value_[kHatY] = new_hat_y; |
| } |
| |
| void InputEventsGenerator::UpdateDeviceFlatMapIfNecessary( |
| GameActivityMotionEvent* android_motion_event, |
| jobject input_device) { |
| int32_t device_id = android_motion_event->deviceId; |
| if (device_flat_.find(device_id) != device_flat_.end()) { |
| // |device_flat_| is already contains the device flat information. |
| return; |
| } |
| float flats[kNumAxes] = {GetFlat(input_device, AMOTION_EVENT_AXIS_X), |
| GetFlat(input_device, AMOTION_EVENT_AXIS_Y), |
| GetFlat(input_device, AMOTION_EVENT_AXIS_Z), |
| GetFlat(input_device, AMOTION_EVENT_AXIS_RZ)}; |
| device_flat_[device_id] = std::vector<float>(flats, flats + kNumAxes); |
| } |
| |
| void InputEventsGenerator::CreateInputEventsFromSbKey(SbKey key, |
| Events* events) { |
| events->clear(); |
| |
| // Press event |
| std::unique_ptr<SbInputData> data(new SbInputData()); |
| memset(data.get(), 0, sizeof(*data)); |
| |
| data->window = window_; |
| data->type = kSbInputEventTypePress; |
| |
| data->device_type = kSbInputDeviceTypeKeyboard; |
| data->device_id = 0; |
| |
| data->key = key; |
| data->key_location = kSbKeyLocationUnspecified; |
| data->key_modifiers = kSbKeyModifiersNone; |
| |
| events->push_back(std::unique_ptr<Event>( |
| new Application::Event(kSbEventTypeInput, data.release(), |
| &Application::DeleteDestructor<SbInputData>))); |
| |
| // Unpress event |
| data.reset(new SbInputData()); |
| memset(data.get(), 0, sizeof(*data)); |
| |
| data->window = window_; |
| data->type = kSbInputEventTypeUnpress; |
| |
| data->device_type = kSbInputDeviceTypeKeyboard; |
| data->device_id = 0; |
| |
| data->key = key; |
| data->key_location = kSbKeyLocationUnspecified; |
| data->key_modifiers = kSbKeyModifiersNone; |
| |
| events->push_back(std::unique_ptr<Event>( |
| new Application::Event(kSbEventTypeInput, data.release(), |
| &Application::DeleteDestructor<SbInputData>))); |
| } |
| |
| } // namespace shared |
| } // namespace android |
| } // namespace starboard |