Skip to content

Commit

Permalink
Improve Experience With Physical Keyboard and Mouse, Now Mouse Pointe…
Browse files Browse the repository at this point in the history
…r is Always Captured and Can Be Released By Pressing ESC on Keyboard or Pressing Back on Android
  • Loading branch information
KreitinnSoftware committed Jan 25, 2025
1 parent a506ef4 commit 15e0f54
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 120 deletions.
82 changes: 45 additions & 37 deletions app/src/main/java/com/micewine/emu/activities/EmulationActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,19 @@ import android.content.IntentFilter
import android.content.SharedPreferences
import android.content.pm.ActivityInfo
import android.content.pm.PackageManager
import android.content.res.Resources
import android.media.AudioManager
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.os.RemoteException
import android.util.DisplayMetrics
import android.util.Log
import android.view.Display
import android.view.InputDevice
import android.view.KeyEvent
import android.view.MenuItem
import android.view.MotionEvent
import android.view.PointerIcon
import android.view.Surface
import android.view.View
import android.view.Window
Expand All @@ -33,6 +32,7 @@ import android.view.WindowManager
import android.view.inputmethod.InputMethodManager
import android.widget.ScrollView
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.view.GravityCompat
Expand Down Expand Up @@ -62,7 +62,6 @@ import com.micewine.emu.controller.ControllerUtils.prepareButtonsAxisValues
import com.micewine.emu.core.ShellLoader
import com.micewine.emu.core.ShellLoader.runCommand
import com.micewine.emu.input.InputEventSender
import com.micewine.emu.input.InputStub
import com.micewine.emu.input.TouchInputHandler
import com.micewine.emu.views.OverlayView
import kotlinx.coroutines.launch
Expand Down Expand Up @@ -196,10 +195,8 @@ class EmulationActivity : AppCompatActivity(), View.OnApplyWindowInsetsListener
}

R.id.openCloseKeyboard -> {
inputManager.apply {
@Suppress("DEPRECATION")
toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, 0)
}
@Suppress("DEPRECATION")
inputManager.toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, 0)

lorieView.requestFocus()

Expand Down Expand Up @@ -243,32 +240,20 @@ class EmulationActivity : AppCompatActivity(), View.OnApplyWindowInsetsListener

mInputHandler = TouchInputHandler(this, InputEventSender(lorieView))
mLorieKeyListener = View.OnKeyListener { _: View?, k: Int, e: KeyEvent ->
if (k == KeyEvent.KEYCODE_BACK) {
if (e.isFromSource(InputDevice.SOURCE_MOUSE) || e.isFromSource(InputDevice.SOURCE_MOUSE_RELATIVE)) {
if (e.repeatCount != 0) // ignore auto-repeat
return@OnKeyListener true
if (e.action == KeyEvent.ACTION_UP || e.action == KeyEvent.ACTION_DOWN) lorieView.sendMouseEvent(
-1f,
-1f,
InputStub.BUTTON_RIGHT,
e.action == KeyEvent.ACTION_DOWN,
true
)
return@OnKeyListener true
if ((k == KeyEvent.KEYCODE_BACK || k == KeyEvent.KEYCODE_ESCAPE) && e.action == MotionEvent.ACTION_UP) {
if (lorieView.hasPointerCapture()) {
lorieView.releasePointerCapture()
}
if (e.scanCode == KEY_BACK && e.device.keyboardType != InputDevice.KEYBOARD_TYPE_ALPHABETIC || e.scanCode == 0) {
if (e.action == KeyEvent.ACTION_UP) if (!drawerLayout?.isDrawerOpen(GravityCompat.START)!!) {

if ((e.action == KeyEvent.ACTION_UP && !lorieView.hasPointerCapture()) || (e.scanCode == KEY_BACK && e.device.keyboardType != InputDevice.KEYBOARD_TYPE_ALPHABETIC || e.scanCode == 0)){
if (!drawerLayout?.isDrawerOpen(GravityCompat.START)!!) {
drawerLayout?.openDrawer(GravityCompat.START)
} else {
drawerLayout?.closeDrawers()
}

inputManager.apply {
hideSoftInputFromWindow(window.decorView.windowToken, 0)
}

return@OnKeyListener true
}

inputManager.hideSoftInputFromWindow(window.decorView.windowToken, 0)
} else if (k == KeyEvent.KEYCODE_VOLUME_DOWN) {
audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC, AudioManager.ADJUST_LOWER, AudioManager.FLAG_SHOW_UI)
return@OnKeyListener true
Expand All @@ -284,7 +269,30 @@ class EmulationActivity : AppCompatActivity(), View.OnApplyWindowInsetsListener
lorieParent.setOnTouchListener { _: View?, e: MotionEvent ->
// Avoid batched MotionEvent objects and reduce potential latency.
// For reference: https://developer.android.com/develop/ui/views/touch-and-input/stylus-input/advanced-stylus-features#rendering.
if (e.action == MotionEvent.ACTION_DOWN) lorieParent.requestUnbufferedDispatch(e)
if (e.action == MotionEvent.ACTION_DOWN) {
lorieParent.requestUnbufferedDispatch(e)
}

when (e.buttonState) {
MotionEvent.BUTTON_PRIMARY -> {
lorieView.requestPointerCapture()
Toast.makeText(this, getString(R.string.mouse_captured), Toast.LENGTH_SHORT).show()
}
MotionEvent.BUTTON_SECONDARY -> {
if (!lorieView.hasPointerCapture()) {
if (!drawerLayout?.isDrawerOpen(GravityCompat.START)!!) {
drawerLayout?.openDrawer(GravityCompat.START)
} else {
drawerLayout?.closeDrawers()
}

inputManager.hideSoftInputFromWindow(window.decorView.windowToken, 0)

return@setOnTouchListener true
}
}
}

mInputHandler!!.handleTouchEvent(lorieParent, lorieView, e)
}
lorieParent.setOnHoverListener { _: View?, e: MotionEvent? ->
Expand Down Expand Up @@ -441,8 +449,7 @@ class EmulationActivity : AppCompatActivity(), View.OnApplyWindowInsetsListener
handler.postDelayed({ this.onPreferencesChangedCallback() }, 100)
}

@SuppressLint("UnsafeIntentLaunch")
fun onPreferencesChangedCallback() {
private fun onPreferencesChangedCallback() {
onWindowFocusChanged(hasWindowFocus())
val lorieView = lorieView

Expand Down Expand Up @@ -473,15 +480,13 @@ class EmulationActivity : AppCompatActivity(), View.OnApplyWindowInsetsListener
super.onPause()
}

val lorieView: LorieView?
private val lorieView: LorieView?
get() = findViewById(R.id.lorieView)

fun handleKey(e: KeyEvent): Boolean {
return mLorieKeyListener!!.onKey(lorieView, e.keyCode, e)
}

var orientation: Int = 0

init {
instance = this
}
Expand Down Expand Up @@ -527,8 +532,6 @@ class EmulationActivity : AppCompatActivity(), View.OnApplyWindowInsetsListener
// We should recover connection in the case if file descriptor for some reason was broken...
if (!connected) {
tryConnect()
} else {
lorieView!!.pointerIcon = PointerIcon.getSystemIcon(this, PointerIcon.TYPE_NULL)
}
}
}
Expand Down Expand Up @@ -556,8 +559,13 @@ class EmulationActivity : AppCompatActivity(), View.OnApplyWindowInsetsListener
}

@JvmStatic
fun getRealMetrics(m: DisplayMetrics?) {
if (getInstance().lorieView != null && getInstance().lorieView!!.display != null) getInstance().lorieView!!.display.getRealMetrics(m)
fun getKeyboardConnected(): Boolean {
return externalKeyboardConnected
}

@JvmStatic
fun getDisplayDensity(): Float {
return Resources.getSystem().displayMetrics.density
}

var sharedLogs: ShellLoader.ViewModelAppLogs? = null
Expand Down
11 changes: 6 additions & 5 deletions app/src/main/java/com/micewine/emu/input/InputEventSender.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import static android.view.KeyEvent.*;
import static android.view.MotionEvent.*;
import static androidx.core.math.MathUtils.clamp;
import static com.micewine.emu.activities.EmulationActivity.getKeyboardConnected;
import static com.micewine.emu.input.InputStub.*;
import static java.nio.charset.StandardCharsets.UTF_8;

Expand All @@ -29,10 +30,7 @@ public final class InputEventSender {
private final InputStub mInjector;

public boolean tapToMove = false;
public boolean preferScancodes = false;
public boolean pointerCapture = false;
public boolean scaleTouchpad = false;
public float capturedPointerSpeedFactor = 100;

/** Set of pressed keys for which we've sent TextEvent. */
private final TreeSet<Integer> mPressedTextKeys;
Expand Down Expand Up @@ -162,9 +160,12 @@ else if (e.getUnicodeChar() != 0)
// For Enter getUnicodeChar() returns 10 (line feed), but we still
// want to send it as KeyEvent.
char unicode = keyCode != KEYCODE_ENTER ? (char) e.getUnicodeChar() : 0;
int scancode = (preferScancodes || !no_modifiers) ? e.getScanCode(): 0;

if (!preferScancodes) {
boolean keyboardIsConnected = getKeyboardConnected();

int scancode = (keyboardIsConnected || !no_modifiers) ? e.getScanCode(): 0;

if (!keyboardIsConnected) {
if (pressed && unicode != 0 && no_modifiers) {
mPressedTextKeys.add(keyCode);
if ((e.getMetaState() & META_ALT_RIGHT_ON) != 0)
Expand Down
99 changes: 21 additions & 78 deletions app/src/main/java/com/micewine/emu/input/TouchInputHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@

package com.micewine.emu.input;

import static com.micewine.emu.activities.EmulationActivity.getDisplayDensity;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.PointF;
import android.hardware.display.DisplayManager;
import android.hardware.input.InputManager;
import android.os.Handler;
import android.os.Build;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.GestureDetector;
import android.view.InputDevice;
Expand Down Expand Up @@ -53,17 +54,6 @@ public class TouchInputHandler {
int TOUCH = 3;
}

@IntDef({CapturedPointerTransformation.AUTO, CapturedPointerTransformation.NONE, CapturedPointerTransformation.COUNTER_CLOCKWISE, CapturedPointerTransformation.UPSIDE_DOWN, CapturedPointerTransformation.CLOCKWISE})
@Retention(RetentionPolicy.SOURCE)
public @interface CapturedPointerTransformation {
// values correspond to transformation needed given getRotation(), e.g. getRotation() = 1 requires counter-clockwise transformation
int AUTO = -1;
int NONE = 0;
int COUNTER_CLOCKWISE = 1;
int UPSIDE_DOWN = 2;
int CLOCKWISE = 3;
}

private final RenderData mRenderData;
private final GestureDetector mScroller;
private final TapGestureDetector mTapDetector;
Expand All @@ -77,7 +67,7 @@ public class TouchInputHandler {
private InputStrategyInterface mInputStrategy;
private final InputEventSender mInjector;
private final EmulationActivity mActivity;
private final DisplayMetrics mMetrics = new DisplayMetrics();
private float mDensity;

private final BiConsumer<Integer, Boolean> noAction = (key, down) -> {};
private final BiConsumer<Integer, Boolean> swipeUpAction = noAction;
Expand Down Expand Up @@ -130,7 +120,6 @@ public void onDisplayChanged(int displayId) {
}
};

@CapturedPointerTransformation static int capturedPointerTransformation = CapturedPointerTransformation.NONE;
private final int[][] buttons = {
{MotionEvent.BUTTON_PRIMARY, InputStub.BUTTON_LEFT},
{MotionEvent.BUTTON_TERTIARY, InputStub.BUTTON_MIDDLE},
Expand Down Expand Up @@ -237,6 +226,7 @@ static public void refreshInputDevices() {
});
android.util.Log.d("DEVICES", "requesting stylus " + stylusAvailable.get());
android.util.Log.d("DEVICES", "external keyboard connected " + externalKeyboardAvailable.get());

LorieView.requestStylusEnabled(stylusAvailable.get());
EmulationActivity.getInstance().setExternalKeyboardConnected(externalKeyboardAvailable.get());
}
Expand Down Expand Up @@ -268,11 +258,9 @@ public boolean handleTouchEvent(View view0, View view, MotionEvent event) {
event.offsetLocation(-offsetX, -offsetY);
}

if (!view.isFocused() && event.getAction() == MotionEvent.ACTION_DOWN)
if (!view.isFocused() && event.getAction() == MotionEvent.ACTION_DOWN) {
view.requestFocus();

if (event.getAction() == MotionEvent.ACTION_UP)
setCapturingEnabled(true);
}

if (!isDexEvent(event) && (event.getToolType(event.getActionIndex()) == MotionEvent.TOOL_TYPE_MOUSE
|| (event.getSource() & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE)
Expand Down Expand Up @@ -357,7 +345,7 @@ public void handleHostSizeChanged(int w, int h) {
mTouchpadHandler.handleHostSizeChanged(w, h);

resetTransformation();
EmulationActivity.getRealMetrics(mMetrics);
mDensity = getDisplayDensity();
}

public void setInputMode(@InputMode int inputMode) {
Expand All @@ -371,13 +359,6 @@ else if (inputMode == InputMode.SIMULATED_TOUCH)
mInputStrategy = new InputStrategyInterface.TrackpadInputStrategy(mInjector);
}

public void setCapturingEnabled(boolean enabled) {
if (mInjector.pointerCapture && enabled)
mActivity.getLorieView().requestPointerCapture();
else
mActivity.getLorieView().releasePointerCapture();
}

public static boolean isExternal(InputDevice d) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
return d.isExternal();
Expand Down Expand Up @@ -443,30 +424,6 @@ private class GestureListener extends GestureDetector.SimpleOnGestureListener
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
int pointerCount = e2.getPointerCount();

// For captured touchpad pointer:
// Automatic (for touchpad) mode is needed because touchpads ignore screen orientation and report physical X and Y
if ((e2.getSource() & InputDevice.SOURCE_TOUCHPAD) == InputDevice.SOURCE_TOUCHPAD
&& mInputStrategy instanceof InputStrategyInterface.TrackpadInputStrategy) {
float temp;
int transform = capturedPointerTransformation == CapturedPointerTransformation.AUTO ?
mDisplayRotation : capturedPointerTransformation;
switch (transform) {
case CapturedPointerTransformation.CLOCKWISE:
temp = distanceX; distanceX = -distanceY; distanceY = temp; break;
case CapturedPointerTransformation.COUNTER_CLOCKWISE:
temp = distanceX;
// noinspection SuspiciousNameCombination
distanceX = distanceY; distanceY = -temp; break;
case CapturedPointerTransformation.UPSIDE_DOWN:
distanceX = -distanceX; distanceY = -distanceY; break;
default:
break;
}
distanceX *= mInjector.capturedPointerSpeedFactor;
distanceY *= mInjector.capturedPointerSpeedFactor;
}


if (pointerCount >= 3 && !mSwipeCompleted) {
// Note that distance values are reversed. For example, dragging a finger in the
// direction of increasing Y coordinate (downwards) results in distanceY being
Expand Down Expand Up @@ -620,7 +577,7 @@ boolean mouseButtonDown(int mask) {
{MotionEvent.BUTTON_SECONDARY, InputStub.BUTTON_RIGHT}
};

/** @noinspection ReassignedVariable, SuspiciousNameCombination*/
/** @noinspection ReassignedVariable */
@SuppressLint("ClickableViewAccessibility")
boolean onTouch(View v, MotionEvent e) {
if (e.getAction() == MotionEvent.ACTION_SCROLL) {
Expand All @@ -631,35 +588,21 @@ boolean onTouch(View v, MotionEvent e) {
return true;
}

if (!v.hasPointerCapture()) {
float scaledX = e.getX() * mRenderData.scale.x, scaledY = e.getY() * mRenderData.scale.y;
if (mRenderData.setCursorPosition(scaledX, scaledY))
mInjector.sendCursorMove(scaledX, scaledY, false);
} else if (e.getAction() == MotionEvent.ACTION_MOVE && e.getPointerCount() == 1) {
boolean axis_relative_x = e.getDevice().getMotionRange(MotionEvent.AXIS_RELATIVE_X) != null;
boolean mouse_relative = (e.getSource() & InputDevice.SOURCE_MOUSE_RELATIVE) == InputDevice.SOURCE_MOUSE_RELATIVE;
if (axis_relative_x || mouse_relative) {
float x = axis_relative_x ? e.getAxisValue(MotionEvent.AXIS_RELATIVE_X) : e.getX();
float y = axis_relative_x ? e.getAxisValue(MotionEvent.AXIS_RELATIVE_Y) : e.getY();
float temp;

switch (capturedPointerTransformation) {
case CapturedPointerTransformation.CLOCKWISE:
temp = x; x = -y; y = temp; break;
case CapturedPointerTransformation.COUNTER_CLOCKWISE:
temp = x; x = y; y = -temp; break;
case CapturedPointerTransformation.UPSIDE_DOWN:
x = -x; y = -y; break;
default:
break;
}
if (v.hasPointerCapture()) {
if (e.getAction() == MotionEvent.ACTION_MOVE && e.getPointerCount() == 1) {
boolean axis_relative_x = e.getDevice().getMotionRange(MotionEvent.AXIS_RELATIVE_X) != null;
boolean mouse_relative = (e.getSource() & InputDevice.SOURCE_MOUSE_RELATIVE) == InputDevice.SOURCE_MOUSE_RELATIVE;
if (axis_relative_x || mouse_relative) {
float x = axis_relative_x ? e.getAxisValue(MotionEvent.AXIS_RELATIVE_X) : e.getX();
float y = axis_relative_x ? e.getAxisValue(MotionEvent.AXIS_RELATIVE_Y) : e.getY();

x *= mInjector.capturedPointerSpeedFactor * mMetrics.density;
y *= mInjector.capturedPointerSpeedFactor * mMetrics.density;
x *= mDensity;
y *= mDensity;

mInjector.sendCursorMove(x, y, true);
if (axis_relative_x && mTouchpadHandler != null)
mTouchpadHandler.mTapDetector.onTouchEvent(e);
mInjector.sendCursorMove(x, y, true);
if (axis_relative_x && mTouchpadHandler != null)
mTouchpadHandler.mTapDetector.onTouchEvent(e);
}
}
}

Expand Down
Loading

0 comments on commit 15e0f54

Please sign in to comment.