[Scummvm-git-logs] scummvm master -> 987becc5297b29fd8fb30c43aff25105e917bc08
antoniou79
a.antoniou79 at gmail.com
Sun Jan 10 20:16:22 UTC 2021
This automated email contains information about 1 new commit which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .
Summary:
987becc529 ANDROID: Fix physical mouse behavior and allow multitouch hold and drag
Commit: 987becc5297b29fd8fb30c43aff25105e917bc08
https://github.com/scummvm/scummvm/commit/987becc5297b29fd8fb30c43aff25105e917bc08
Author: antoniou (a.antoniou79 at gmail.com)
Date: 2021-01-10T22:10:29+02:00
Commit Message:
ANDROID: Fix physical mouse behavior and allow multitouch hold and drag
Also added various comments to document behavior.
Minor JE_MULTI event (Fix concerned bad brackets code in the events.cpp for JE_MULTI (multiple fingers held down)
) fix and comments for mouse behavior
Allow onHover to catch the mouse events instead of OnTrackBallEvent()
Also the "system back" button is ignored for the Trackball too because we treat it as mouse in isMouse(e) check
Add multitouch handler class. Handling and early filtering of multitouch events is moved in the new class.
Changed paths:
A backends/platform/android/org/scummvm/scummvm/MultitouchHelper.java
backends/platform/android/android.cpp
backends/platform/android/android.h
backends/platform/android/events.cpp
backends/platform/android/org/scummvm/scummvm/EditableSurfaceView.java
backends/platform/android/org/scummvm/scummvm/MouseHelper.java
backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java
backends/platform/android/org/scummvm/scummvm/ScummVMEventsBase.java
dists/android/README.Android
diff --git a/backends/platform/android/android.cpp b/backends/platform/android/android.cpp
index 5d9ae9bb11..e08464a8f5 100644
--- a/backends/platform/android/android.cpp
+++ b/backends/platform/android/android.cpp
@@ -104,7 +104,10 @@ OSystem_Android::OSystem_Android(int audio_sample_rate, int audio_buffer_size) :
_touchpad_mode(true),
_touchpad_scale(66),
_dpad_scale(4),
- _fingersDown(0),
+// _fingersDown(0),
+ _firstPointerId(-1),
+ _secondPointerId(-1),
+ _thirdPointerId(-1),
_trackball_scale(2),
_joystick_scale(10) {
diff --git a/backends/platform/android/android.h b/backends/platform/android/android.h
index fc870692c9..cacd16aef1 100644
--- a/backends/platform/android/android.h
+++ b/backends/platform/android/android.h
@@ -101,7 +101,7 @@ private:
uint32 _queuedEventTime;
Common::Mutex *_event_queue_lock;
- Common::Point _touch_pt_down, _touch_pt_scroll, _touch_pt_dt;
+ Common::Point _touch_pt_down, _touch_pt_scroll, _touch_pt_dt, _touch_pt_multi;
int _eventScaleX;
int _eventScaleY;
bool _touchpad_mode;
@@ -109,7 +109,11 @@ private:
int _trackball_scale;
int _dpad_scale;
int _joystick_scale;
- int _fingersDown;
+// int _fingersDown;
+ int _firstPointerId;
+ int _secondPointerId;
+ int _thirdPointerId;
+
void pushEvent(const Common::Event &event);
diff --git a/backends/platform/android/events.cpp b/backends/platform/android/events.cpp
index 25b646b2d8..3527dbd708 100644
--- a/backends/platform/android/events.cpp
+++ b/backends/platform/android/events.cpp
@@ -378,14 +378,17 @@ void OSystem_Android::pushEvent(int type, int arg1, int arg2, int arg3,
switch (type) {
case JE_SYS_KEY:
+ // fall through
case JE_KEY:
switch (arg1) {
case AKEY_EVENT_ACTION_DOWN:
e.type = Common::EVENT_KEYDOWN;
break;
+
case AKEY_EVENT_ACTION_UP:
e.type = Common::EVENT_KEYUP;
break;
+
default:
LOGE("unhandled jaction on key: %d", arg1);
return;
@@ -403,63 +406,81 @@ void OSystem_Android::pushEvent(int type, int arg1, int arg2, int arg3,
e.kbd.keycode = jkeymap[arg2];
}
- if (arg5 > 0)
+ if (arg5 > 0) {
e.kbdRepeat = true;
+ }
// map special keys to 'our' ascii codes
switch (e.kbd.keycode) {
case Common::KEYCODE_BACKSPACE:
- LOGD("received BACKSPACE");
+// LOGD("received BACKSPACE");
e.kbd.ascii = Common::ASCII_BACKSPACE;
break;
+
case Common::KEYCODE_TAB:
e.kbd.ascii = Common::ASCII_TAB;
break;
+
case Common::KEYCODE_RETURN:
e.kbd.ascii = Common::ASCII_RETURN;
break;
+
case Common::KEYCODE_ESCAPE:
e.kbd.ascii = Common::ASCII_ESCAPE;
break;
+
case Common::KEYCODE_SPACE:
e.kbd.ascii = Common::ASCII_SPACE;
break;
+
case Common::KEYCODE_F1:
e.kbd.ascii = Common::ASCII_F1;
break;
+
case Common::KEYCODE_F2:
e.kbd.ascii = Common::ASCII_F2;
break;
+
case Common::KEYCODE_F3:
e.kbd.ascii = Common::ASCII_F3;
break;
+
case Common::KEYCODE_F4:
e.kbd.ascii = Common::ASCII_F4;
break;
+
case Common::KEYCODE_F5:
e.kbd.ascii = Common::ASCII_F5;
break;
+
case Common::KEYCODE_F6:
e.kbd.ascii = Common::ASCII_F6;
break;
+
case Common::KEYCODE_F7:
e.kbd.ascii = Common::ASCII_F7;
break;
+
case Common::KEYCODE_F8:
e.kbd.ascii = Common::ASCII_F8;
break;
+
case Common::KEYCODE_F9:
e.kbd.ascii = Common::ASCII_F9;
break;
+
case Common::KEYCODE_F10:
e.kbd.ascii = Common::ASCII_F10;
break;
+
case Common::KEYCODE_F11:
e.kbd.ascii = Common::ASCII_F11;
break;
+
case Common::KEYCODE_F12:
e.kbd.ascii = Common::ASCII_F12;
break;
+
default:
e.kbd.ascii = arg3;
break;
@@ -467,8 +488,9 @@ void OSystem_Android::pushEvent(int type, int arg1, int arg2, int arg3,
// arg4 is the metastate of the key press event
// check for "Shift" key modifier
- if (arg4 & AMETA_SHIFT_MASK)
+ if (arg4 & AMETA_SHIFT_MASK) {
e.kbd.flags |= Common::KBD_SHIFT;
+ }
// We revert the commit to disable the Alt modifier
// TODO revisit this old comment from commit https://github.com/scummvm/scummvm/commit/bfecb37501b6fdc35f2802216db5fb2b0e54b8ee
@@ -523,8 +545,11 @@ void OSystem_Android::pushEvent(int type, int arg1, int arg2, int arg3,
case JE_DPAD:
switch (arg2) {
case AKEYCODE_DPAD_UP:
+ // fall through
case AKEYCODE_DPAD_DOWN:
+ // fall through
case AKEYCODE_DPAD_LEFT:
+ // fall through
case AKEYCODE_DPAD_RIGHT:
if (arg1 != AKEY_EVENT_ACTION_DOWN)
return;
@@ -549,10 +574,11 @@ void OSystem_Android::pushEvent(int type, int arg1, int arg2, int arg3,
// TODO put these values in some option dlg?
int f = CLIP(arg5, 1, 8) * _dpad_scale * 100 / s;
- if (arg2 == AKEYCODE_DPAD_UP || arg2 == AKEYCODE_DPAD_LEFT)
+ if (arg2 == AKEYCODE_DPAD_UP || arg2 == AKEYCODE_DPAD_LEFT) {
*c -= f;
- else
+ } else {
*c += f;
+ }
}
pushEvent(e);
@@ -564,9 +590,11 @@ void OSystem_Android::pushEvent(int type, int arg1, int arg2, int arg3,
case AKEY_EVENT_ACTION_DOWN:
e.type = Common::EVENT_LBUTTONDOWN;
break;
+
case AKEY_EVENT_ACTION_UP:
e.type = Common::EVENT_LBUTTONUP;
break;
+
default:
LOGE("unhandled jaction on dpad key: %d", arg1);
return;
@@ -580,12 +608,14 @@ void OSystem_Android::pushEvent(int type, int arg1, int arg2, int arg3,
}
case JE_DOWN:
+// LOGD("JE_DOWN");
_touch_pt_down = dynamic_cast<AndroidGraphicsManager *>(_graphicsManager)->getMousePosition();
_touch_pt_scroll.x = -1;
_touch_pt_scroll.y = -1;
break;
case JE_SCROLL:
+// LOGD("JE_SCROLL");
e.type = Common::EVENT_MOUSEMOVE;
if (_touchpad_mode) {
@@ -609,10 +639,10 @@ void OSystem_Android::pushEvent(int type, int arg1, int arg2, int arg3,
return;
case JE_TAP:
- if (_fingersDown > 0) {
- _fingersDown = 0;
- return;
- }
+ // arg1 = mouse x
+ // arg2 = mouse y
+ // arg3 is (int)(e.getEventTime() - e.getDownTime())
+// LOGD("JE_TAP - arg3 %d", arg3);
e.type = Common::EVENT_MOUSEMOVE;
@@ -626,25 +656,47 @@ void OSystem_Android::pushEvent(int type, int arg1, int arg2, int arg3,
{
Common::EventType down, up;
- // TODO put these values in some option dlg?
+ // Based on this check, we check how long the tap finger was held down
+ // and distinguish cases for:
+ // > 1.0 seconds: Middle Mouse Button
+ // > 0.5 seconds: Right Mouse Button
+ // < 0.5 seconds: Left Mouse Button
+ // Due to how these are detected we cannot have hold and move detection for any of them
+ // They only act as clicks
+ // Note: for hold one finger and move gesture, this is the "move cursor around" action.
+ // Thus, the simple tap and hold cannot be used for "hold left mouse button and drag" behavior.
+ // This is the reason we use "double tap and move" to emulate that specific behavior.
+ // TODO This might be unwanted "alternate" behavior (better to have it as optional?)
+ // TODO put these time (in milliseconds) values in some option dlg?
if (arg3 > 1000) {
+// LOGD("JE_TAP - arg3 %d --> Middle Mouse Button", arg3);
down = Common::EVENT_MBUTTONDOWN;
up = Common::EVENT_MBUTTONUP;
} else if (arg3 > 500) {
+// LOGD("JE_TAP - arg3 %d --> Right Mouse Button", arg3);
down = Common::EVENT_RBUTTONDOWN;
up = Common::EVENT_RBUTTONUP;
} else {
+// LOGD("JE_TAP - arg3 %d --> Left Mouse Button", arg3);
down = Common::EVENT_LBUTTONDOWN;
up = Common::EVENT_LBUTTONUP;
}
_event_queue_lock->lock();
- if (_queuedEventTime)
+// LOGD("JE_TAP - _queuedEventTime %d ", _queuedEventTime);
+ if (_queuedEventTime) {
+ // if another event is queued to be served by PollEvent (but not in _event_queue)
+ // at the time of this JE_TAP event
+ // then push that pending _queuedEvent event to the queue for direct service
+ // This is done because we will replace the _queuedEvent below
+// LOGD("JE_TAP -pushing old _queuedEvent to event queue");
_event_queue.push(_queuedEvent);
+ }
- if (!_touchpad_mode)
+ if (!_touchpad_mode) {
_event_queue.push(e);
+ }
e.type = down;
_event_queue.push(e);
@@ -659,6 +711,11 @@ void OSystem_Android::pushEvent(int type, int arg1, int arg2, int arg3,
return;
case JE_DOUBLE_TAP:
+ // arg1 = mouse x
+ // arg2 = mouse y
+ // arg3 = AMOTION_EVENT_ACTION_DOWN, AMOTION_EVENT_ACTION_UP, AMOTION_EVENT_ACTION_MOVE
+// LOGD("JE_DOUBLE_TAP - arg3 %d", arg3);
+
e.type = Common::EVENT_MOUSEMOVE;
if (_touchpad_mode) {
@@ -677,11 +734,13 @@ void OSystem_Android::pushEvent(int type, int arg1, int arg2, int arg3,
_touch_pt_dt.x = -1;
_touch_pt_dt.y = -1;
break;
+
case AMOTION_EVENT_ACTION_UP:
dptype = Common::EVENT_LBUTTONUP;
break;
- // held and moved
+
case AMOTION_EVENT_ACTION_MOVE:
+ // held and moved
if (_touch_pt_dt.x == -1 && _touch_pt_dt.y == -1) {
_touch_pt_dt.x = arg1;
_touch_pt_dt.y = arg2;
@@ -697,8 +756,9 @@ void OSystem_Android::pushEvent(int type, int arg1, int arg2, int arg3,
}
break;
+
default:
- LOGE("unhandled jaction on double tap: %d", arg3);
+ LOGE("JE_DOUBLE_TAP - unhandled jaction on double tap: %d", arg3);
return;
}
@@ -712,54 +772,200 @@ void OSystem_Android::pushEvent(int type, int arg1, int arg2, int arg3,
return;
case JE_MULTI:
- switch (arg2) {
- case AMOTION_EVENT_ACTION_POINTER_DOWN:
- if (arg1 > _fingersDown)
- _fingersDown = arg1;
-
- return;
+ // Documentation: https://developer.android.com/training/gestures/multi
+ // TODO also look into: https://developer.android.com/training/gestures/movement
+ // for possible tweaks to gesture detection
+ // arg1 = fingers down
+ // arg2 = AMOTION_EVENT_ACTION_POINTER_DOWN, AMOTION_EVENT_ACTION_POINTER_UP, CANCEL, OUTSIDE, MOVE
+ // we handle CANCEL and OUTSIDE externally
+ //
+ // TODO Other related events for Android multitouch gestures events are:
+ // - ACTION_DOWN â For the first pointer that touches the screen. This starts the gesture. The pointer data for this pointer is always at index 0 in the MotionEvent.
+ // - ACTION_MOVE â A change has happened during a press gesture
+ // - ACTION_UP â Sent when the last pointer leaves the screen.
+// LOGD("JE_MULTI - fingersDown=%d arg2=%d", arg1, arg2);
- case AMOTION_EVENT_ACTION_POINTER_UP:
- if (arg1 != _fingersDown)
- return;
+ e.type = Common::EVENT_MOUSEMOVE;
- {
- Common::EventType up;
+ if (_touchpad_mode) {
+ e.mouse = dynamic_cast<AndroidGraphicsManager *>(_graphicsManager)->getMousePosition();
+ } else {
+ e.mouse.x = arg3;
+ e.mouse.y = arg4;
+ }
- switch (_fingersDown) {
- case 1:
- e.type = Common::EVENT_RBUTTONDOWN;
- up = Common::EVENT_RBUTTONUP;
- break;
+ {
+ Common::EventType multitype = Common::EVENT_INVALID;
+
+ switch (arg2 & AMOTION_EVENT_ACTION_MASK) {
+ case AMOTION_EVENT_ACTION_POINTER_DOWN:
+ // (From Android Developers documentation)
+ // [This event is fired] For extra pointers that enter the screen beyond the first.
+ // The pointer data for this pointer is at the index returned by getActionIndex()
+// LOGD("AMOTION_EVENT_ACTION_POINTER_DOWN fingersDown: %d", arg1);
+// if (arg1 > _fingersDown) {
+// LOGD("AMOTION_EVENT_ACTION_POINTER_DOWN changing _fingersDown to arg1: %d", arg1);
+// _fingersDown = arg1;
+// }
+
+ _touch_pt_multi.x = -1;
+ _touch_pt_multi.y = -1;
+
+ // TODO also handle Cancel event
+ // TODO also handle going from 3 to 2 fingers
+ // TODO also handle case of receiving a new ACTION_DOWN without first having received an ACTION_UP
+ switch (arg1) {
case 2:
- e.type = Common::EVENT_MBUTTONDOWN;
- up = Common::EVENT_MBUTTONUP;
+// LOGD("AMOTION_EVENT_ACTION_POINTER_DOWN arg1: %d --> Right Mouse Button", arg1);
+ multitype = Common::EVENT_RBUTTONDOWN;
+ //up = Common::EVENT_RBUTTONUP;
+// // Don't add immediately in case it is superseded by a third finger --> middle mouse button
+ break;
+
+ case 3:
+// if (_queuedEventTime && _queuedEvent.type == Common::EVENT_RBUTTONDOWN) {
+// _queuedEventTime = 0; // cancel right mouse button down
+// LOGD("AMOTION_EVENT_ACTION_POINTER_DOWN arg1: %d --> Middle Mouse Button", arg1);
+// multitype = Common::EVENT_MBUTTONDOWN;
+// }
+ multitype = Common::EVENT_MBUTTONDOWN;
+ //up = Common::EVENT_MBUTTONUP;
break;
+
default:
- LOGD("unmapped multi tap: %d", _fingersDown);
+ LOGE("AMOTION_EVENT_ACTION_POINTER_DOWN - unmapped multi tap (arg1): %d", arg1);
return;
}
- e.mouse = dynamic_cast<AndroidGraphicsManager *>(_graphicsManager)->getMousePosition();
+ break;
- _event_queue_lock->lock();
+ case AMOTION_EVENT_ACTION_CANCEL:
+// LOGD("AMOTION_EVENT_ACTION_CANCEL - (arg1): %d", arg1);
+ return;
- if (_queuedEventTime)
- _event_queue.push(_queuedEvent);
+ case AMOTION_EVENT_ACTION_OUTSIDE:
+// LOGD("AMOTION_EVENT_ACTION_OUTSIDE - (arg1): %d", arg1);
+ return;
- _event_queue.push(e);
+ case AMOTION_EVENT_ACTION_POINTER_UP:
+ // (From Android Developers documentation)
+ // Sent when a non-primary pointer goes up.
+// LOGD("AMOTION_EVENT_ACTION_POINTER_UP arg1: %d", arg1);
+// if (arg1 != _fingersDown) {
+// LOGD("returning as AMOTION_EVENT_ACTION_POINTER_UP arg1 != _fingersDown");
+// _fingersDown = 0;
+// return;
+// }
- e.type = up;
- _queuedEvent = e;
- _queuedEventTime = getMillis() + kQueuedInputEventDelay;
+ //Common::EventType up;
- _event_queue_lock->unlock();
- return;
+ switch (arg1) {
+ case 2:
+// LOGD("AMOTION_EVENT_ACTION_POINTER_UP arg1: %d --> Right Mouse Button", arg1);
+ //e.type = Common::EVENT_RBUTTONDOWN;
+ //up = Common::EVENT_RBUTTONUP;
+ multitype = Common::EVENT_RBUTTONUP;
+ break;
+
+ case 3:
+// LOGD("AMOTION_EVENT_ACTION_POINTER_UP arg1: %d --> Middle Mouse Button", arg1);
+ //e.type = Common::EVENT_MBUTTONDOWN;
+ //up = Common::EVENT_MBUTTONUP;
+ multitype = Common::EVENT_MBUTTONUP;
+ break;
+
+ default:
+ LOGE("AMOTION_EVENT_ACTION_POINTER_UP - unmapped multi tap (arg1): %d", arg1);
+ return;
+ }
+
+// e.mouse = dynamic_cast<AndroidGraphicsManager *>(_graphicsManager)->getMousePosition();
+//
+// _event_queue_lock->lock();
+//
+// LOGD("AMOTION_EVENT_ACTION_POINTER_UP - _queuedEventTime %d ", _queuedEventTime);
+// if (_queuedEventTime) {
+// LOGD("AMOTION_EVENT_ACTION_POINTER_UP -pushing _queuedEvent (up) to event queue");
+// _event_queue.push(_queuedEvent);
+// }
+//
+// _event_queue.push(e);
+//
+// e.type = up;
+// _queuedEvent = e;
+// _queuedEventTime = getMillis() + kQueuedInputEventDelay;
+//
+// _event_queue_lock->unlock();
+ break;
+
+ case AMOTION_EVENT_ACTION_MOVE:
+ // TODO wrong event?
+ // https://developer.android.com/training/gestures/movement
+ // https://developer.android.com/training/gestures/multi
+ //
+// LOGD("AMOTION_EVENT_ACTION_MOVE arg1: %d", arg1);
+ // held and moved
+ if (_touch_pt_multi.x == -1 && _touch_pt_multi.y == -1) {
+ _touch_pt_multi.x = arg3;
+ _touch_pt_multi.y = arg4;
+ return;
+ }
+
+ multitype = Common::EVENT_MOUSEMOVE;
+
+ // TODO TO TEST for non-touchpad mode too!
+ if (_touchpad_mode) {
+ e.mouse.x = (arg3 - _touch_pt_multi.x) * 100 / _touchpad_scale;
+ e.mouse.y = (arg4 - _touch_pt_multi.y) * 100 / _touchpad_scale;
+ e.mouse += _touch_pt_down; // TODO maybe we need another reference point???
+ }
+ break;
default:
- LOGE("unhandled jaction on multi tap: %d", arg2);
+ LOGE("JE_MULTI - unhandled jaction on multi tap: %d", arg2);
return;
}
+
+ _event_queue_lock->lock();
+ if (_queuedEventTime) {
+// // if another event is queued to be served by PollEvent (but not in _event_queue)
+// // at the time of this JE_MULTI event
+// // then push that pending _queuedEvent event to the queue for direct service
+// // This is done because we will push a new event to the queue and also might replace the _queuedEvent below
+// LOGD("JE_MULTI -pushing old _queuedEvent to event queue");
+ _event_queue.push(_queuedEvent);
+ }
+// if (_queuedEventTime && _queuedEvent.type != Common::EVENT_RBUTTONDOWN) {
+// // if another event is queued to be served by PollEvent (but not in _event_queue)
+// // and it is not Common::EVENT_RBUTTONDOWN (which is only created by the JE_MULTI event as a _queuedEvent)
+// // at the time of this JE_MULTI event
+// // then push that pending _queuedEvent event to the queue for direct service
+// // This is done because we will push a new event to the queue and also might replace the _queuedEvent below
+// LOGD("JE_MULTI -pushing old _queuedEvent to event queue");
+// _event_queue.push(_queuedEvent);
+// }
+
+ // push the default move event
+ _event_queue.push(e);
+ e.type = multitype;
+
+ if (e.type != Common::EVENT_INVALID) {
+// if (e.type == Common::EVENT_RBUTTONDOWN) {
+// _queuedEvent = e; // enqueue the Right Button DOWN event with a 200 ms delay
+// _queuedEventTime = getMillis() + 200; // TODO add to constants like kQueuedInputEventDelay is
+// } else {
+// // TODO what if this is a quick Common::EVENT_RBUTTONUP?
+// if (_queuedEventTime && _queuedEvent.type == Common::EVENT_RBUTTONDOWN && e.type == Common::EVENT_RBUTTONUP) {
+// _event_queue.push(_queuedEvent);
+// _queuedEvent = e; // enqueue the Right Button UP event with a kQueuedInputEventDelay
+// _queuedEventTime = getMillis() + kQueuedInputEventDelay;
+// } else {
+ _event_queue.push(e);
+// }
+// }
+ }
+
+ _event_queue_lock->unlock();
}
return;
@@ -771,9 +977,11 @@ void OSystem_Android::pushEvent(int type, int arg1, int arg2, int arg3,
case AMOTION_EVENT_ACTION_DOWN:
e.type = Common::EVENT_LBUTTONDOWN;
break;
+
case AMOTION_EVENT_ACTION_UP:
e.type = Common::EVENT_LBUTTONUP;
break;
+
case AMOTION_EVENT_ACTION_MOVE:
e.type = Common::EVENT_MOUSEMOVE;
@@ -782,8 +990,9 @@ void OSystem_Android::pushEvent(int type, int arg1, int arg2, int arg3,
e.mouse.y += arg3 * _trackball_scale / _eventScaleY;
break;
+
default:
- LOGE("unhandled jaction on system key: %d", arg1);
+ LOGE("unhandled JE_BALL jaction on system key: %d", arg1);
return;
}
@@ -1050,6 +1259,11 @@ bool OSystem_Android::pollEvent(Common::Event &event) {
_event_queue_lock->lock();
if (_queuedEventTime && (getMillis() > _queuedEventTime)) {
+// if (_queuedEvent.type == Common::EVENT_RBUTTONDOWN) {
+// LOGD("Executing delayed RIGHT MOUSE DOWN!!!!");
+// } else if (_queuedEvent.type == Common::EVENT_RBUTTONUP) {
+// LOGD("Executing delayed RIGHT MOUSE UP!!!!");
+// }
event = _queuedEvent;
_queuedEventTime = 0;
// _event_queue_lock->unlock();
@@ -1057,10 +1271,10 @@ bool OSystem_Android::pollEvent(Common::Event &event) {
} else if (_event_queue.empty()) {
_event_queue_lock->unlock();
return false;
+
} else {
event = _event_queue.pop();
}
-
_event_queue_lock->unlock();
if (Common::isMouseEvent(event)) {
diff --git a/backends/platform/android/org/scummvm/scummvm/EditableSurfaceView.java b/backends/platform/android/org/scummvm/scummvm/EditableSurfaceView.java
index 94395db5c2..631f411bdb 100644
--- a/backends/platform/android/org/scummvm/scummvm/EditableSurfaceView.java
+++ b/backends/platform/android/org/scummvm/scummvm/EditableSurfaceView.java
@@ -1,6 +1,7 @@
package org.scummvm.scummvm;
import android.content.Context;
+import android.content.Intent;
import android.os.Build;
import android.text.Editable;
import android.text.InputType;
@@ -18,15 +19,22 @@ import android.annotation.TargetApi;
public class EditableSurfaceView extends SurfaceView {
final Context _context;
+ final boolean _allowHideSystemMousePointer;
+ private boolean _mouseIsInCapturedState;
+
public EditableSurfaceView(Context context) {
super(context);
_context = context;
+ _mouseIsInCapturedState = false;
+ _allowHideSystemMousePointer = true;
}
public EditableSurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
_context = context;
+ _mouseIsInCapturedState = false;
+ _allowHideSystemMousePointer = true;
}
public EditableSurfaceView(Context context,
@@ -34,6 +42,8 @@ public class EditableSurfaceView extends SurfaceView {
int defStyle) {
super(context, attrs, defStyle);
_context = context;
+ _mouseIsInCapturedState = false;
+ _allowHideSystemMousePointer = true;
}
@Override
@@ -288,8 +298,30 @@ public class EditableSurfaceView extends SurfaceView {
return new MyInputConnection();
}
+ public void showSystemMouseCursor(boolean show) {
+ //Log.d(ScummVM.LOG_TAG, "captureMouse::showSystemMouseCursor2 " + show);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ // Android N (Nougat) is Android 7.0
+ //SurfaceView main_surface = findViewById(R.id.main_surface);
+ int type = show ? PointerIcon.TYPE_DEFAULT : PointerIcon.TYPE_NULL;
+ // https://stackoverflow.com/a/55482761
+ //Log.d(ScummVM.LOG_TAG, "captureMouse::showSystemMouseCursor3a");
+ setPointerIcon(PointerIcon.getSystemIcon(_context, type));
+ } else {
+ /* Currently hiding the system mouse cursor is only
+ supported on OUYA. If other systems provide similar
+ intents, please add them here as well */
+ Intent intent =
+ new Intent(show?
+ "tv.ouya.controller.action.SHOW_CURSOR" :
+ "tv.ouya.controller.action.HIDE_CURSOR");
+ //Log.d(ScummVM.LOG_TAG, "captureMouse::showSystemMouseCursor3b");
+ _context.sendBroadcast(intent);
+ }
+ }
+
// This re-inforces the code for hiding the system mouse.
- // We already had code for this in ScummVMActivity (see showMouseCursor())
+ // We already had code for this in ScummVMActivity (see showSystemMouseCursor())
// so this might be redundant
//
// It applies on devices running Android 7 and above
@@ -299,34 +331,78 @@ public class EditableSurfaceView extends SurfaceView {
@TargetApi(24)
@Override
public PointerIcon onResolvePointerIcon(MotionEvent me, int pointerIndex) {
- return PointerIcon.getSystemIcon(_context, PointerIcon.TYPE_NULL);
+ if (_allowHideSystemMousePointer) {
+ if (_mouseIsInCapturedState) {
+ return PointerIcon.getSystemIcon(_context, PointerIcon.TYPE_NULL);
+ } else {
+ return PointerIcon.getSystemIcon(_context, PointerIcon.TYPE_DEFAULT);
+ }
+ } else {
+ return PointerIcon.getSystemIcon(_context, PointerIcon.TYPE_DEFAULT);
+ }
}
- public void captureMouse(boolean capture) {
- final boolean bGlobalsHideSystemMousePointer = true;
+ public void captureMouse(final boolean capture) {
+
+ if ((!_mouseIsInCapturedState && ((ScummVMActivity)_context).isKeyboardOverlayShown() && capture)) {
+ //Log.d(ScummVM.LOG_TAG, "captureMouse::returned - keyboard is shown");
+ return;
+ }
+
+ if ((capture && _mouseIsInCapturedState) ||
+ (!capture && ! _mouseIsInCapturedState)) {
+ //Log.d(ScummVM.LOG_TAG, "captureMouse::returned - nothing to do");
+ return;
+ }
+
if (capture) {
- setFocusableInTouchMode(true);
- setFocusable(true);
- requestFocus();
- if (bGlobalsHideSystemMousePointer && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O ) {
- postDelayed( new Runnable() {
- public void run()
- {
- Log.v(ScummVM.LOG_TAG, "captureMouse::requestPointerCapture() delayed");
- requestPointerCapture();
- }
- }, 50 );
- }
+// setFocusableInTouchMode(true);
+// setFocusable(true);
+// requestFocus();
+ _mouseIsInCapturedState = true;
+ //Log.d(ScummVM.LOG_TAG, "captureMouse::_mouseIsInCapturedState");
} else {
- if (bGlobalsHideSystemMousePointer && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O ) {
- postDelayed( new Runnable() {
- public void run() {
- Log.v(ScummVM.LOG_TAG, "captureMouse::releasePointerCapture()");
- releasePointerCapture();
- }
- }, 50 );
- }
+ //Log.d(ScummVM.LOG_TAG, "captureMouse::no _mouseIsInCapturedState");
+ _mouseIsInCapturedState = false;
}
+
+ showSystemMouseCursor(!capture);
+
+ // trying capturing the pointer resulted in firing TrackBallEvent instead of HoverEvent for our SurfaceView
+ // also the behavior was inconsistent when resuming to the app from a pause
+// if (_allowHideSystemMousePointer) {
+// // for API 26 and above
+// if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
+// Log.d(ScummVM.LOG_TAG, "captureMouse::CODES 0");
+// if (capture) {
+// postDelayed( new Runnable() {
+// public void run()
+// {
+// Log.v(ScummVM.LOG_TAG, "captureMouse::requestPointerCapture() delayed");
+//// if (!hasPointerCapture()) {
+// requestPointerCapture();
+//// showSystemMouseCursor(!capture);
+//// }
+// }
+// }, 50 );
+// } else {
+// if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
+// postDelayed(new Runnable() {
+// public void run() {
+// Log.v(ScummVM.LOG_TAG, "captureMouse::releasePointerCapture()");
+//// if (hasPointerCapture()) {
+// releasePointerCapture();
+//// showSystemMouseCursor(!capture);
+//// }
+// }
+// }, 50);
+// }
+// }
+// } else {
+// Log.d(ScummVM.LOG_TAG, "captureMouse::NO CODES 0 showSystemMouseCursor " + !capture);
+// showSystemMouseCursor(!capture);
+// }
+// }
}
}
diff --git a/backends/platform/android/org/scummvm/scummvm/MouseHelper.java b/backends/platform/android/org/scummvm/scummvm/MouseHelper.java
index 4f54708e2d..f808d39358 100644
--- a/backends/platform/android/org/scummvm/scummvm/MouseHelper.java
+++ b/backends/platform/android/org/scummvm/scummvm/MouseHelper.java
@@ -1,17 +1,19 @@
package org.scummvm.scummvm;
import android.annotation.SuppressLint;
+import android.os.Build;
+//import android.util.Log;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
-import android.view.SurfaceView;
+//import android.view.SurfaceView;
import android.view.View;
/**
* Contains helper methods for mouse/hover events that were introduced in Android 4.0.
*/
-public class MouseHelper {
- private final View.OnHoverListener _listener;
+public class MouseHelper implements View.OnHoverListener {
+ //private final View.OnHoverListener _listener;
private final ScummVM _scummvm;
private boolean _rmbPressed;
private boolean _lmbPressed;
@@ -40,28 +42,78 @@ public class MouseHelper {
public MouseHelper(ScummVM scummvm) {
_scummvm = scummvm;
- _listener = createListener();
+ //_listener = createListener();
}
- private View.OnHoverListener createListener() {
- return new View.OnHoverListener() {
- @Override
- public boolean onHover(View view, MotionEvent e) {
- return onMouseEvent(e, true);
- }
- };
+// private View.OnHoverListener createListener() {
+// return new View.OnHoverListener() {
+// @Override
+// public boolean onHover(View view, MotionEvent e) {
+// Log.d(ScummVM.LOG_TAG, "onHover mouseEvent");
+// return onMouseEvent(e, true);
+// }
+// };
+// }
+
+ @Override
+ public boolean onHover(View view, MotionEvent motionEvent) {
+ //Log.d(ScummVM.LOG_TAG, "onHover mouseEvent");
+ return onMouseEvent(motionEvent, true);
+// return false;
}
- public void attach(SurfaceView main_surface) {
- main_surface.setOnHoverListener(_listener);
+// public void attach(SurfaceView main_surface) {
+// main_surface.setOnHoverListener(_listener);
+// }
+
+ // isTrackball is a subcase of isMouse (meaning isMouse will also return true)
+ public static boolean isTrackball(KeyEvent e) {
+ if (e == null) {
+ return false;
+ }
+
+ int source = e.getSource();
+ return ((source & InputDevice.SOURCE_TRACKBALL) == InputDevice.SOURCE_TRACKBALL) ||
+ (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && ((source & InputDevice.SOURCE_MOUSE_RELATIVE) == InputDevice.SOURCE_MOUSE_RELATIVE));
+
+
+ }
+
+ // isTrackball is a subcase of isMouse (meaning isMouse will also return true)
+ public static boolean isTrackball(MotionEvent e) {
+ if (e == null) {
+ return false;
+ }
+ //int source = e.getSource();
+
+ InputDevice device = e.getDevice();
+
+ if (device == null) {
+ return false;
+ }
+
+ int sources = device.getSources();
+
+ return ((sources & InputDevice.SOURCE_TRACKBALL) == InputDevice.SOURCE_TRACKBALL) ||
+ (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && ((sources & InputDevice.SOURCE_MOUSE_RELATIVE) == InputDevice.SOURCE_MOUSE_RELATIVE));
+
}
public static boolean isMouse(KeyEvent e) {
+ if (e == null) {
+ return false;
+ }
+
int source = e.getSource();
- return ((source & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE) ||
- ((source & InputDevice.SOURCE_STYLUS) == InputDevice.SOURCE_STYLUS) ||
- ((source & InputDevice.SOURCE_TOUCHPAD) == InputDevice.SOURCE_TOUCHPAD);
+ //Log.d(ScummVM.LOG_TAG, "isMouse keyEvent source: " + source);
+
+ // SOURCE_MOUSE_RELATIVE is sent when mouse is detected as trackball
+ // TODO: why does this happen? Do we need to also check for SOURCE_TRACKBALL here?
+ return ((source & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE)
+ || ((source & InputDevice.SOURCE_STYLUS) == InputDevice.SOURCE_STYLUS)
+ || ((source & InputDevice.SOURCE_TOUCHPAD) == InputDevice.SOURCE_TOUCHPAD)
+ || isTrackball(e);
}
public static boolean isMouse(MotionEvent e) {
@@ -77,23 +129,33 @@ public class MouseHelper {
int sources = device.getSources();
- return ((sources & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE) ||
- ((sources & InputDevice.SOURCE_STYLUS) == InputDevice.SOURCE_STYLUS) ||
- ((sources & InputDevice.SOURCE_TOUCHPAD) == InputDevice.SOURCE_TOUCHPAD);
+ // SOURCE_MOUSE_RELATIVE is sent when mouse is detected as trackball
+ // TODO: why does this happen? Do we need to also check for SOURCE_TRACKBALL here?
+ return ((sources & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE)
+ || ((sources & InputDevice.SOURCE_STYLUS) == InputDevice.SOURCE_STYLUS)
+ || ((sources & InputDevice.SOURCE_TOUCHPAD) == InputDevice.SOURCE_TOUCHPAD)
+ || isTrackball(e);
}
private boolean handleButton(MotionEvent e, boolean mbPressed, int mask, int downEvent, int upEvent) {
boolean mbDown = (e.getButtonState() & mask) == mask;
+ if ((e.getSource() & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE
+ && (e.getButtonState() & MotionEvent.BUTTON_BACK) == MotionEvent.BUTTON_BACK) {
+ mbDown = (mask == MotionEvent.BUTTON_SECONDARY);
+ }
+
if (mbDown) {
if (!mbPressed) {
- // left mouse button was pressed just now
+ // mouse button was pressed just now
+ //Log.d(ScummVM.LOG_TAG, "handleButton mbDown, not mbPressed, mask = " + mask);
_scummvm.pushEvent(downEvent, (int)e.getX(), (int)e.getY(), e.getButtonState(), 0, 0, 0);
}
return true;
} else {
if (mbPressed) {
- // left mouse button was released just now
+ //Log.d(ScummVM.LOG_TAG, "handleButton not mbDown, mbPressed, mask = " + mask);
+ // mouse button was released just now
_scummvm.pushEvent(upEvent, (int)e.getX(), (int)e.getY(), e.getButtonState(), 0, 0, 0);
}
@@ -103,10 +165,17 @@ public class MouseHelper {
@SuppressLint("InlinedApi")
public boolean onMouseEvent(MotionEvent e, boolean hover) {
- _scummvm.pushEvent(ScummVMEventsBase.JE_MOUSE_MOVE, (int)e.getX(), (int)e.getY(), 0, 0, 0, 0);
+
+ _scummvm.pushEvent(ScummVMEventsBase.JE_MOUSE_MOVE,
+ (int) e.getX(),
+ (int) e.getY(),
+ 0,
+ 0, 0, 0);
int buttonState = e.getButtonState();
+ //Log.d(ScummVM.LOG_TAG, "onMouseEvent buttonState = " + buttonState);
+
boolean lmbDown = (buttonState & MotionEvent.BUTTON_PRIMARY) == MotionEvent.BUTTON_PRIMARY;
if (!hover && e.getAction() != MotionEvent.ACTION_UP && buttonState == 0) {
@@ -145,4 +214,6 @@ public class MouseHelper {
return true;
}
+
+
}
diff --git a/backends/platform/android/org/scummvm/scummvm/MultitouchHelper.java b/backends/platform/android/org/scummvm/scummvm/MultitouchHelper.java
new file mode 100644
index 0000000000..9d0cc2bf15
--- /dev/null
+++ b/backends/platform/android/org/scummvm/scummvm/MultitouchHelper.java
@@ -0,0 +1,268 @@
+
+package org.scummvm.scummvm;
+
+import android.os.Message;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.os.Handler;
+
+import androidx.annotation.NonNull;
+
+import java.lang.ref.WeakReference;
+
+import static org.scummvm.scummvm.ScummVMEventsBase.JE_MULTI;
+
+public class MultitouchHelper {
+ private final ScummVM _scummvm;
+
+ private boolean _candidateStartOfMultitouchSession;
+ // a flag indicating whether we are in multitouch mode (more than one fingers down)
+ private boolean _multitouchMode;
+
+ // within a multitouch session (until a cancel event or no more multiple fingers down) the IDs for each finger-pointer is persisted and is consistent across events
+ // we can use the ids to track and utilize the movement of a specific finger (while ignoring the rest)
+ // Currently, we are using the last finger down, as the finger that moves the cursor
+ private int _firstPointerId;
+ private int _secondPointerId;
+ private int _thirdPointerId;
+
+ private int _cachedActionEventOnPointer2DownX;
+ private int _cachedActionEventOnPointer2DownY;
+ // The "level" of multitouch that is detected.
+ // We do not support downgrading a level, ie. if a three finger multitouch is detected,
+ // then raising one finger will void the multitouch session,
+ // rather than revert to two fingers multitouch.
+ // Similarly we do not support upgrading a level, ie. if we are already handling a two finger multitouch,
+ // then putting down another finger will void the session,
+ // rather than upgrade it to three fingers multitouch.
+ // TODO for this purpose we need to allow some limited time limit (delay _kLevelDecisionDelayMs) before deciding
+ // if the user did a two finger multitouch or intents to do a three finger multitouch
+ // Valid values for _multitouchLevel: 0, 2, 3
+ private int _multitouchLevel;
+
+ private final int _kLevelDecisionDelayMs = 400; // in milliseconds
+
+ // messages for MultitouchHelperHandler
+ final static int MSG_MT_DECIDE_MULTITOUCH_SESSION_TIMEDOUT = 1;
+ final static int MSG_MT_UPGRADE_TO_LEVEL_3_TIMEDOUT = 2;
+
+ final private MultitouchHelper.MultitouchHelperHandler _multiTouchLevelUpgradeHandler = new MultitouchHelper.MultitouchHelperHandler(this);
+
+ // constructor
+ public MultitouchHelper(ScummVM scummvm) {
+ _scummvm = scummvm;
+
+ _multitouchMode = false;
+ _multitouchLevel = 0;
+ _candidateStartOfMultitouchSession = false;
+
+ resetPointers();
+ }
+
+ public void resetPointers() {
+ _firstPointerId = -1;
+ _secondPointerId = -1;
+ _thirdPointerId = -1;
+ }
+
+ public boolean isMultitouchMode() {
+ return _multitouchMode;
+ }
+ public int getMultitouchLevel() {
+ return _multitouchLevel;
+ }
+
+ public void setMultitouchMode(boolean enabledFlg) {
+ _multitouchMode = enabledFlg;
+ }
+ public void setMultitouchLevel(int mtlevel) {
+ _multitouchLevel = mtlevel;
+ }
+
+
+ // TODO Maybe for consistency purposes, maybe sent all (important) UP events that were not sent, when ending a multitouch session?
+ public boolean handleMotionEvent(final MotionEvent event) {
+
+ // constants from APIv5:
+ // (action & ACTION_POINTER_INDEX_MASK) >> ACTION_POINTER_INDEX_SHIFT
+ //final int pointer = (action & 0xff00) >> 8;
+
+ int pointerIndex = -1;
+ int actionEventX;
+ int actionEventY;
+
+ if (event.getAction() == MotionEvent.ACTION_DOWN) {
+ // start of a multitouch session! one finger down -- this is sent for the first pointer who touches the screen
+ resetPointers();
+ setMultitouchLevel(0);
+ setMultitouchMode(false);
+ _candidateStartOfMultitouchSession = true;
+ _multiTouchLevelUpgradeHandler.clear();
+// _multiTouchLevelUpgradeHandler.sendMessageDelayed(_multiTouchLevelUpgradeHandler.obtainMessage(MSG_MT_DECIDE_MULTITOUCH_SESSION_TIMEDOUT), _kLevelDecisionDelayMs);
+ pointerIndex = 0;
+ _firstPointerId = event.getPointerId(pointerIndex);
+ // TODO - do we want this as true?
+ return false;
+ } else if (event.getAction() == MotionEvent.ACTION_CANCEL) {
+ resetPointers();
+ setMultitouchLevel(0);
+ setMultitouchMode(false);
+ _multiTouchLevelUpgradeHandler.clear();
+ return true;
+ } else if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
+ return false;
+ }
+
+ if (event.getPointerCount() > 1 && event.getPointerCount() < 4) {
+ // a multi-touch event
+ if (_candidateStartOfMultitouchSession && event.getPointerCount() > 1) {
+ setMultitouchMode(true);
+ }
+
+ if (isMultitouchMode()) {
+ if ((event.getAction() & MotionEvent.ACTION_POINTER_DOWN) == MotionEvent.ACTION_POINTER_DOWN) {
+ pointerIndex = event.getActionIndex();
+ if (event.getPointerCount() == 2) {
+ _secondPointerId = event.getPointerId(pointerIndex);
+ if (getMultitouchLevel() == 0) {
+ _multiTouchLevelUpgradeHandler.removeMessages(MSG_MT_UPGRADE_TO_LEVEL_3_TIMEDOUT);
+ if (pointerIndex != -1) {
+ _cachedActionEventOnPointer2DownX = (int) event.getX(pointerIndex);
+ _cachedActionEventOnPointer2DownY = (int) event.getY(pointerIndex);
+ } else {
+ _cachedActionEventOnPointer2DownX = -1;
+ _cachedActionEventOnPointer2DownY = -1;
+ }
+ _multiTouchLevelUpgradeHandler.sendMessageDelayed(_multiTouchLevelUpgradeHandler.obtainMessage(MSG_MT_UPGRADE_TO_LEVEL_3_TIMEDOUT), _kLevelDecisionDelayMs);
+ }
+ return true;
+ } else if (event.getPointerCount() == 3) {
+ _thirdPointerId = event.getPointerId(pointerIndex);
+ if (getMultitouchLevel() == 0) {
+ setMultitouchLevel(3);
+ }
+ }
+ } else {
+ if (event.getPointerCount() == 2) {
+ // we prioritize the second pointer/ finger
+ pointerIndex = event.findPointerIndex(_secondPointerId);
+
+ if (getMultitouchLevel() == 0
+ && ((event.getAction() & MotionEvent.ACTION_POINTER_UP) == MotionEvent.ACTION_POINTER_UP
+ || (event.getAction() & MotionEvent.ACTION_MOVE) == MotionEvent.ACTION_MOVE)) {
+ setMultitouchLevel(2);
+ _multiTouchLevelUpgradeHandler.removeMessages(MSG_MT_UPGRADE_TO_LEVEL_3_TIMEDOUT);
+
+ if (pointerIndex != -1) {
+ actionEventX = (int)event.getX(pointerIndex);
+ actionEventY = (int)event.getY(pointerIndex);
+ } else {
+ actionEventX = -1;
+ actionEventY = -1;
+ }
+
+ // send the missing pointer down event first
+ _scummvm.pushEvent(JE_MULTI,
+ event.getPointerCount(),
+ MotionEvent.ACTION_POINTER_DOWN,
+ actionEventX,
+ actionEventY,
+ 0, 0);
+ }
+
+ } else if (event.getPointerCount() == 3) {
+ // we prioritize the third pointer/ finger
+ pointerIndex = event.findPointerIndex(_thirdPointerId);
+ }
+ }
+
+// if (pointerIndex == -1) {
+// Log.d(ScummVM.LOG_TAG,"Warning: pointerIndex == -1 and getPointerCount = " + event.getPointerCount());
+// }
+
+ if (pointerIndex != -1) {
+ actionEventX = (int)event.getX(pointerIndex);
+ actionEventY = (int)event.getY(pointerIndex);
+ } else {
+ actionEventX = -1;
+ actionEventY = -1;
+ }
+
+ // we are only concerned for events with fingers down equal to the decided level of multitouch session
+ if (getMultitouchLevel() == event.getPointerCount()) {
+ // arg1 will be the number of fingers down in the MULTI event we send to events.cpp
+ _scummvm.pushEvent(JE_MULTI,
+ event.getPointerCount(),
+ event.getAction(),
+ actionEventX,
+ actionEventY,
+ 0, 0);
+ }
+ }
+ return true;
+
+ } else if (event.getPointerCount() >= 4) {
+ // ignore if more than 3 fingers down. Mark as multitouch "handled" (return true)
+ return true;
+
+ } else if (event.getPointerCount() == 1 && isMultitouchMode() ) {
+ // keep ignoring events until we exit multitouch mode "session"
+ // this is to catch the case of progressively lifting fingers and being left with only one finger still touching the surface
+ return true;
+ } else {
+ // one finger, no active multitouch mode "session". Mark as unhandled.
+ return false;
+ }
+ }
+
+
+ // Custom handler code (to avoid mem leaks, see warning "This Handler Class Should Be Static Or Leaks Might Occurâ) based on:
+ // https://stackoverflow.com/a/27826094
+ public static class MultitouchHelperHandler extends Handler {
+
+ private final WeakReference<MultitouchHelper> mListenerReference;
+
+ public MultitouchHelperHandler(MultitouchHelper listener) {
+ mListenerReference = new WeakReference<>(listener);
+ }
+
+ @Override
+ public synchronized void handleMessage(@NonNull Message msg) {
+ MultitouchHelper listener = mListenerReference.get();
+ if(listener != null) {
+ listener.handle_MTHH_Message(msg);
+ }
+ }
+
+ public void clear() {
+ this.removeCallbacksAndMessages(null);
+ }
+ }
+
+ private void handle_MTHH_Message(final Message msg) {
+ if (msg.what == MSG_MT_UPGRADE_TO_LEVEL_3_TIMEDOUT) {
+ if (getMultitouchLevel() == 0) {
+ // window of allowing upgrade to level 3 timed out, decide level two.
+ setMultitouchLevel(2);
+
+ // send the delayed pointer down event
+ _scummvm.pushEvent(JE_MULTI,
+ 2,
+ MotionEvent.ACTION_POINTER_DOWN,
+ _cachedActionEventOnPointer2DownX,
+ _cachedActionEventOnPointer2DownY,
+ 0, 0);
+ }
+ }
+// else if (msg.what == MSG_MT_DECIDE_MULTITOUCH_SESSION_TIMEDOUT) {
+// if (_candidateStartOfMultitouchSession && !isMultitouchMode()) {
+// // window of considering touch as start of multitouch event timed out. Clear the candidate flag.
+// _candidateStartOfMultitouchSession = false;
+// }
+// }
+ }
+
+ public void clearEventHandler() {
+ _multiTouchLevelUpgradeHandler.clear();
+ }
+}
diff --git a/backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java b/backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java
index 3b218a2ab1..072945fbae 100644
--- a/backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java
+++ b/backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java
@@ -142,6 +142,10 @@ public class ScummVMActivity extends Activity implements OnKeyboardVisibilityLis
return getResources().getConfiguration().keyboard == KEYBOARD_QWERTY;
}
+ public boolean isKeyboardOverlayShown() {
+ return keyboardWithoutTextInputShown;
+ }
+
public void showScreenKeyboardWithoutTextInputField(final int keyboard) {
if (_main_surface != null) {
_inputManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
@@ -149,7 +153,9 @@ public class ScummVMActivity extends Activity implements OnKeyboardVisibilityLis
keyboardWithoutTextInputShown = true;
runOnUiThread(new Runnable() {
public void run() {
+ //Log.d(ScummVM.LOG_TAG, "showScreenKeyboardWithoutTextInputField - captureMouse(false)");
_main_surface.captureMouse(false);
+ //_main_surface.showSystemMouseCursor(true);
if (keyboard == 0) {
// TODO do we need SHOW_FORCED HERE?
//_inputManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
@@ -476,7 +482,9 @@ public class ScummVMActivity extends Activity implements OnKeyboardVisibilityLis
DimSystemStatusBar.get().dim(_videoLayout);
//DimSystemStatusBar.get().dim(_main_surface);
+ //Log.d(ScummVM.LOG_TAG, "showScreenKeyboardWithoutTextInputField - captureMouse(true)");
_main_surface.captureMouse(true);
+ //_main_surface.showSystemMouseCursor(false);
}
});
}
@@ -497,8 +505,9 @@ public class ScummVMActivity extends Activity implements OnKeyboardVisibilityLis
if (bGlobalsCompatibilityHacksTextInputEmulatesHwKeyboard) {
showScreenKeyboardWithoutTextInputField(dGlobalsTextInputKeyboard);
- //Log.d(ScummVM.LOG_TAG, "showScreenKeyboard - showScreenKeyboardWithoutTextInputField()");
+ //Log.d(ScummVM.LOG_TAG, "showScreenKeyboard - captureMouse(false)");
_main_surface.captureMouse(false);
+ //_main_surface.showSystemMouseCursor(true);
return;
}
//Log.d(ScummVM.LOG_TAG, "showScreenKeyboard: YOU SHOULD NOT SEE ME!!!");
@@ -517,7 +526,9 @@ public class ScummVMActivity extends Activity implements OnKeyboardVisibilityLis
if (_main_surface != null) {
if (keyboardWithoutTextInputShown) {
showScreenKeyboardWithoutTextInputField(dGlobalsTextInputKeyboard);
+ //Log.d(ScummVM.LOG_TAG, "hideScreenKeyboard - captureMouse(true)");
_main_surface.captureMouse(true);
+ //_main_surface.showSystemMouseCursor(false);
}
}
}
@@ -877,11 +888,13 @@ public class ScummVMActivity extends Activity implements OnKeyboardVisibilityLis
_videoLayout.addView(_toggleKeyboardBtnIcon, keybrdBtnlayout);
_videoLayout.bringChildToFront(_toggleKeyboardBtnIcon);
- _main_surface.captureMouse(true);
- // REDUNDANT?
- if ( android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N ) {
- _main_surface.setPointerIcon(android.view.PointerIcon.getSystemIcon(this, android.view.PointerIcon.TYPE_NULL));
- }
+ _main_surface.setFocusable(true);
+ _main_surface.setFocusableInTouchMode(true);
+ _main_surface.requestFocus();
+
+ //Log.d(ScummVM.LOG_TAG, "onCreate - captureMouse(true)");
+ //_main_surface.captureMouse(true, true);
+ //_main_surface.showSystemMouseCursor(false);
// TODO is this redundant since we call hideSystemUI() ?
DimSystemStatusBar.get().dim(_videoLayout);
@@ -944,9 +957,10 @@ public class ScummVMActivity extends Activity implements OnKeyboardVisibilityLis
});
Log.d(ScummVM.LOG_TAG, "Hover available: " + _hoverAvailable);
+ _mouseHelper = null;
if (_hoverAvailable) {
_mouseHelper = new MouseHelper(_scummvm);
- _mouseHelper.attach(_main_surface);
+// _mouseHelper.attach(_main_surface);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {
@@ -964,6 +978,9 @@ public class ScummVMActivity extends Activity implements OnKeyboardVisibilityLis
_main_surface.setOnKeyListener(_events);
_main_surface.setOnTouchListener(_events);
+ if (_mouseHelper != null) {
+ _main_surface.setOnHoverListener(_mouseHelper);
+ }
_scummvm_thread = new Thread(_scummvm, "ScummVM");
_scummvm_thread.start();
@@ -972,14 +989,14 @@ public class ScummVMActivity extends Activity implements OnKeyboardVisibilityLis
@Override
public void onStart() {
- Log.d(ScummVM.LOG_TAG, "onStart");
+// Log.d(ScummVM.LOG_TAG, "onStart");
super.onStart();
}
@Override
public void onResume() {
- Log.d(ScummVM.LOG_TAG, "onResume");
+// Log.d(ScummVM.LOG_TAG, "onResume");
// _isPaused = false;
@@ -987,12 +1004,14 @@ public class ScummVMActivity extends Activity implements OnKeyboardVisibilityLis
if (_scummvm != null)
_scummvm.setPause(false);
- showMouseCursor(false);
+ //_main_surface.showSystemMouseCursor(false);
+ //Log.d(ScummVM.LOG_TAG, "onResume - captureMouse(true)");
+ _main_surface.captureMouse(true);
}
@Override
public void onPause() {
- Log.d(ScummVM.LOG_TAG, "onPause");
+// Log.d(ScummVM.LOG_TAG, "onPause");
// _isPaused = true;
@@ -1000,19 +1019,22 @@ public class ScummVMActivity extends Activity implements OnKeyboardVisibilityLis
if (_scummvm != null)
_scummvm.setPause(true);
- showMouseCursor(true);
+ //_main_surface.showSystemMouseCursor(true);
+ //Log.d(ScummVM.LOG_TAG, "onPause - captureMouse(false)");
+ _main_surface.captureMouse(false);
+
}
@Override
public void onStop() {
- Log.d(ScummVM.LOG_TAG, "onStop");
+// Log.d(ScummVM.LOG_TAG, "onStop");
super.onStop();
}
@Override
public void onDestroy() {
- Log.d(ScummVM.LOG_TAG, "onDestroy");
+// Log.d(ScummVM.LOG_TAG, "onDestroy");
super.onDestroy();
@@ -1117,13 +1139,16 @@ public class ScummVMActivity extends Activity implements OnKeyboardVisibilityLis
return false;
}
-
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) {
hideSystemUI();
}
+// showSystemMouseCursor(false);
+// } else {
+// showSystemMouseCursor(true);
+// }
}
// TODO setSystemUiVisibility is introduced in API 11 and deprecated in API 30 - When we move to API 30 we will have to replace this code
@@ -1207,27 +1232,6 @@ public class ScummVMActivity extends Activity implements OnKeyboardVisibilityLis
}
}
- private void showMouseCursor(boolean show) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
- // Android N (Nougat) is Android 7.0
- //SurfaceView main_surface = findViewById(R.id.main_surface);
- if (_main_surface != null) {
- int type = show ? PointerIcon.TYPE_DEFAULT : PointerIcon.TYPE_NULL;
- // https://stackoverflow.com/a/55482761
- _main_surface.setPointerIcon(PointerIcon.getSystemIcon(this, type));
- }
- } else {
- /* Currently hiding the system mouse cursor is only
- supported on OUYA. If other systems provide similar
- intents, please add them here as well */
- Intent intent =
- new Intent(show?
- "tv.ouya.controller.action.SHOW_CURSOR" :
- "tv.ouya.controller.action.HIDE_CURSOR");
- sendBroadcast(intent);
- }
- }
-
// Listener to check for keyboard visibility changes
// https://stackoverflow.com/a/36259261
private void setKeyboardVisibilityListener(final OnKeyboardVisibilityListener onKeyboardVisibilityListener) {
diff --git a/backends/platform/android/org/scummvm/scummvm/ScummVMEventsBase.java b/backends/platform/android/org/scummvm/scummvm/ScummVMEventsBase.java
index d4d50cd2ee..220016d6d7 100644
--- a/backends/platform/android/org/scummvm/scummvm/ScummVMEventsBase.java
+++ b/backends/platform/android/org/scummvm/scummvm/ScummVMEventsBase.java
@@ -5,6 +5,7 @@ import android.os.Handler;
import android.os.Message;
import android.content.Context;
//import android.util.Log;
+import android.util.Log;
import android.view.KeyEvent;
import android.view.KeyCharacterMap;
import android.view.MotionEvent;
@@ -51,8 +52,9 @@ public class ScummVMEventsBase implements
final protected Context _context;
final protected ScummVM _scummvm;
final protected GestureDetector _gd;
- final protected int _longPress;
+ final protected int _longPressTimeout;
final protected MouseHelper _mouseHelper;
+ final protected MultitouchHelper _multitouchHelper;
// Custom handler code (to avoid mem leaks, see warning "This Handler Class Should Be Static Or Leaks Might Occurâ) based on:
// https://stackoverflow.com/a/27826094
@@ -92,14 +94,16 @@ public class ScummVMEventsBase implements
public ScummVMEventsBase(Context context, ScummVM scummvm, MouseHelper mouseHelper) {
_context = context;
_scummvm = scummvm;
+ // Careful, _mouseHelper can be null (if HoverListener is not available for the device API -- old devices, API < 9)
_mouseHelper = mouseHelper;
+ _multitouchHelper = new MultitouchHelper(_scummvm);
+
_gd = new GestureDetector(context, this);
_gd.setOnDoubleTapListener(this);
_gd.setIsLongpressEnabled(false);
- _longPress = ViewConfiguration.getLongPressTimeout();
-
+ _longPressTimeout = ViewConfiguration.getLongPressTimeout();
}
private void handleEVHMessage(final Message msg) {
@@ -132,6 +136,7 @@ public class ScummVMEventsBase implements
public void clearEventHandler() {
_skeyHandler.clear();
+ _multitouchHelper.clearEventHandler();
}
final public void sendQuitEvent() {
@@ -139,14 +144,16 @@ public class ScummVMEventsBase implements
}
public boolean onTrackballEvent(MotionEvent e) {
+ //Log.d(ScummVM.LOG_TAG, "SCUMMV-EVENTS-BASE - onTrackballEvent");
_scummvm.pushEvent(JE_BALL, e.getAction(),
- (int)(e.getX() * e.getXPrecision() * 100),
- (int)(e.getY() * e.getYPrecision() * 100),
- 0, 0, 0);
+ (int)(e.getX() * e.getXPrecision() * 100),
+ (int)(e.getY() * e.getYPrecision() * 100),
+ 0, 0, 0);
return true;
}
public boolean onGenericMotionEvent(MotionEvent e) {
+ // we don't manage the GenericMotionEvent
return false;
}
@@ -233,7 +240,7 @@ public class ScummVMEventsBase implements
// Upon pressing the system menu or system back key:
// (The example below assumes that system Back key was pressed)
// 1. keyHandler.hasMessages(MSG_SBACK_LONG_PRESS) = false, and thus: fired = true
- // 2. Action will be KeyEvent.ACTION_DOWN, so a delayed message "MSG_SBACK_LONG_PRESS" will be sent to keyHandler after _longPress time
+ // 2. Action will be KeyEvent.ACTION_DOWN, so a delayed message "MSG_SBACK_LONG_PRESS" will be sent to keyHandler after _longPressTimeout time
// The "MSG_SBACK_LONG_PRESS" will be handled (and removed) in the keyHandler.
// For the Back button, the keyHandler should forward a ACTION_UP for MENU (the alternate func of Back key!) to native)
// But if the code enters this section before the "MSG_SBACK_LONG_PRESS" was handled in keyHandler (probably due to a ACTION_UP)
@@ -250,7 +257,7 @@ public class ScummVMEventsBase implements
_skeyHandler.removeMessages(typeOfLongPressMessage);
if (action == KeyEvent.ACTION_DOWN) {
- _skeyHandler.sendMessageDelayed(_skeyHandler.obtainMessage(typeOfLongPressMessage), _longPress);
+ _skeyHandler.sendMessageDelayed(_skeyHandler.obtainMessage(typeOfLongPressMessage), _longPressTimeout);
return true;
} else if (action != KeyEvent.ACTION_UP) {
return true;
@@ -261,6 +268,7 @@ public class ScummVMEventsBase implements
}
// It's still necessary to send a key down event to the backend.
+// Log.d(ScummVM.LOG_TAG, "JE_SYS_KEY");
_scummvm.pushEvent(JE_SYS_KEY,
KeyEvent.ACTION_DOWN,
keyCode,
@@ -370,21 +378,67 @@ public class ScummVMEventsBase implements
return true;
}
+
+ /** Aux method to provide a description for a MotionEvent action
+ * Given an action int, returns a string description
+ * Use for debug purposes
+ * @param action the id of the action (as returned by getAction()
+ * @return the action description
+ */
+ public static String motionEventActionToString(int action) {
+ switch (action) {
+
+ case MotionEvent.ACTION_DOWN: return "Down";
+ case MotionEvent.ACTION_MOVE: return "Move";
+ case MotionEvent.ACTION_POINTER_DOWN: return "Pointer Down";
+ case MotionEvent.ACTION_UP: return "Up";
+ case MotionEvent.ACTION_POINTER_UP: return "Pointer Up";
+ case MotionEvent.ACTION_OUTSIDE: return "Outside";
+ case MotionEvent.ACTION_CANCEL: return "Cancel";
+// case MotionEvent.ACTION_POINTER_2_DOWN: return "Pointer 2 Down"; // 261 - deprecated (but still fired for Android 9, Mi device)
+// case MotionEvent.ACTION_POINTER_2_UP: return "Pointer 2 Up"; // 262 - deprecated (but still fired for Android 9, Mi device)
+// case MotionEvent.ACTION_POINTER_3_DOWN: return "Pointer 3 Down"; // 517 - deprecated (but still fired for Android 9, Mi device)
+// case MotionEvent.ACTION_POINTER_3_UP: return "Pointer 3 Up"; // 518 - deprecated (but still fired for Android 9, Mi device)
+ default:
+ if ((action & MotionEvent.ACTION_POINTER_DOWN) == MotionEvent.ACTION_POINTER_DOWN) {
+ return "Pointer Down ***";
+ } else if ((action & MotionEvent.ACTION_POINTER_UP) == MotionEvent.ACTION_POINTER_UP) {
+ return "Pointer Up ***";
+ }
+ return "Unknown:: " + action;
+ }
+ }
+
// OnTouchListener
@Override
final public boolean onTouch(View v, final MotionEvent event) {
-// String actionStr = "";
-// switch (event.getAction()) {
-// case MotionEvent.ACTION_UP:
-// actionStr = "MotionEvent.ACTION_UP";
-// break;
-// case MotionEvent.ACTION_DOWN:
-// actionStr = "MotionEvent.ACTION_DOWN";
-// break;
-// default:
-// actionStr = event.toString();
+
+ // Note: In this article https://developer.android.com/training/gestures/multi
+ // it is recommended to use MotionEventCompat helper methods, instead of directly using MotionEvent getAction() etc.
+ // However, getActionMasked() and MotionEventCompat *are deprecated*, and now direct use of MotionEvent methods is recommended.
+ // https://developer.android.com/reference/androidx/core/view/MotionEventCompat
+
+ // Note 2: Do not return intentionally false for the onTouch function because then getPointerCount() won't work as intended
+ // ie. it will always return 1,
+ // as noted here:
+ // https://stackoverflow.com/a/11709964
+
+ final int action = event.getAction();
+
+ // Get the index of the pointer associated with the action.
+// int index = event.getActionIndex();
+// int xPos = (int)event.getX(index);
+// int yPos = (int)event.getY(index);
+
+// String prefixDBGMsg = "SPECIAL DBG action is " + motionEventActionToString(action) + " ";
+// if (event.getPointerCount() > 1) {
+// // The coordinates of the current screen contact, relative to
+// // the responding View or Activity.
+// Log.d(ScummVM.LOG_TAG,prefixDBGMsg + "Multitouch event (" + event.getPointerCount() + "):: x:" + xPos + " y: " + yPos);
+// } else {
+// // Single touch event
+// Log.d(ScummVM.LOG_TAG,prefixDBGMsg + "Single touch event:: x: " + xPos + " y: " + yPos);
// }
-// Log.d(ScummVM.LOG_TAG, "SCUMMV-EVENTS-BASE - onTOUCH event" + actionStr);
if (ScummVMActivity.keyboardWithoutTextInputShown
&& ((ScummVMActivity) _context).isScreenKeyboardShown()
@@ -404,10 +458,8 @@ public class ScummVMEventsBase implements
}
}
- final int action = event.getAction();
-
// Deal with LINT warning "ScummVMEvents#onTouch should call View#performClick when a click is detected"
- switch (event.getAction()) {
+ switch (action) {
case MotionEvent.ACTION_UP:
v.performClick();
break;
@@ -416,13 +468,9 @@ public class ScummVMEventsBase implements
default:
break;
}
- // constants from APIv5:
- // (action & ACTION_POINTER_INDEX_MASK) >> ACTION_POINTER_INDEX_SHIFT
- final int pointer = (action & 0xff00) >> 8;
- if (pointer > 0) {
- _scummvm.pushEvent(JE_MULTI, pointer, action & 0xff, // ACTION_MASK
- (int)event.getX(), (int)event.getY(), 0, 0);
+ // check if the event can be handled as a multitouch event
+ if (_multitouchHelper.handleMotionEvent(event)) {
return true;
}
@@ -432,7 +480,7 @@ public class ScummVMEventsBase implements
// OnGestureListener
@Override
final public boolean onDown(MotionEvent e) {
- //Log.d(ScummVM.LOG_TAG, "SCUMMV-EVENTS-BASE - onDOWN MotionEvent");
+// Log.d(ScummVM.LOG_TAG, "SCUMMV-EVENTS-BASE - onDOWN MotionEvent");
_scummvm.pushEvent(JE_DOWN, (int)e.getX(), (int)e.getY(), 0, 0, 0, 0);
return true;
}
@@ -455,6 +503,7 @@ public class ScummVMEventsBase implements
@Override
final public boolean onScroll(MotionEvent e1, MotionEvent e2,
float distanceX, float distanceY) {
+// Log.d(ScummVM.LOG_TAG, "onScroll");
_scummvm.pushEvent(JE_SCROLL, (int)e1.getX(), (int)e1.getY(),
(int)e2.getX(), (int)e2.getY(), 0, 0);
@@ -467,6 +516,7 @@ public class ScummVMEventsBase implements
@Override
final public boolean onSingleTapUp(MotionEvent e) {
+// Log.d(ScummVM.LOG_TAG, "onSingleTapUp");
_scummvm.pushEvent(JE_TAP, (int)e.getX(), (int)e.getY(),
(int)(e.getEventTime() - e.getDownTime()), 0, 0, 0);
@@ -476,19 +526,33 @@ public class ScummVMEventsBase implements
// OnDoubleTapListener
@Override
final public boolean onDoubleTap(MotionEvent e) {
+// Log.d(ScummVM.LOG_TAG, "onDoubleTap");
return true;
}
@Override
final public boolean onDoubleTapEvent(MotionEvent e) {
- _scummvm.pushEvent(JE_DOUBLE_TAP, (int)e.getX(), (int)e.getY(),
- e.getAction(), 0, 0, 0);
+ //if the second tap hadn't been released and it's being moved
+// if (e.getAction() == MotionEvent.ACTION_MOVE) {
+// Log.d(ScummVM.LOG_TAG, "onDoubleTapEvent Moving X: " + Float.toString(e.getRawX()) + " Y: " + Float.toString(e.getRawY()));
+// } else if(e.getAction() == MotionEvent.ACTION_UP) {
+// //user released the screen
+// Log.d(ScummVM.LOG_TAG, "onDoubleTapEvent Release");
+// } else if(e.getAction() == MotionEvent.ACTION_DOWN) {
+// Log.d(ScummVM.LOG_TAG, "onDoubleTapEvent DOWN");
+// } else {
+// Log.d(ScummVM.LOG_TAG, "onDoubleTapEvent UNKNOWN!!!!");
+// }
+ _scummvm.pushEvent(JE_DOUBLE_TAP, (int)e.getX(), (int)e.getY(), e.getAction(), 0, 0, 0);
return true;
}
@Override
final public boolean onSingleTapConfirmed(MotionEvent e) {
+ // Note, timing thresholds for double tap detection seem to be hardcoded in the frameworl
+ // as ViewConfiguration.getDoubleTapTimeout()
+// Log.d(ScummVM.LOG_TAG, "onSingleTapConfirmed - double tap failed");
return true;
}
}
diff --git a/dists/android/README.Android b/dists/android/README.Android
index 31249ceaad..c64da66b09 100644
--- a/dists/android/README.Android
+++ b/dists/android/README.Android
@@ -32,23 +32,27 @@ CONTROLS
In touchpad mode, the mouse cursor is independent of the touched point, it
is moved relative to its current position - like on a touchpad.
- The mode can be toggled with the "Touchpad mouse mode" on the Control tab
+ The mode can be toggled with the "Touchpad mouse mode" on the Backend tab
in ScummVM's own option dialog.
Tap + movement: Mouse movement
Tap without movement: Left mouse button click
Tap held for >0.5s without movement: Right mouse button click
Tap held for >1s without movement: Middle mouse button click
- Double Tap + movement: Drag and drop
+ Double Tap + movement: Drag-and-drop
+ or (for some games): emulates left mouse button hold and drag,
+ eg. for selection from action wheel in Curse of Monkey Island
On devices supporting multitouch:
Two finger tap: Right mouse button click
+ Two finger tap + movement of second finger: Right mouse button hold and drag,
+ eg. for selection from action wheel in Tony Tough
Three finger tap: Middle mouse button click
+ Three finger tap + movement of third finger: Middle mouse button hold and drag
System keys
- Back button: Escape
+ Back button: Escape/Skip
Menu button: ScummVM menu
Menu button held for 0.5s: Toggle virtual keyboard
- Camera or Search button: Right mouse button click
More information about the Scummvm-git-logs
mailing list