diff --git a/.gitignore b/.gitignore index e0ab95a517..a1cb0faa22 100644 --- a/.gitignore +++ b/.gitignore @@ -41,4 +41,5 @@ build/ app/.externalNativeBuild/ # NDK stuff -.cxx/ \ No newline at end of file +.cxx/ +app/src/main/jni/moonlight-core/Build.txt diff --git a/README.md b/README.md index 0b1bbe780e..c9075e5e29 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,35 @@ +# 首个实现多点触控 灵敏度调节 (触点移速调节) 的 Moonlight分支.
+# The first Moonlight fork to implement multi-touch sensitivity tweak (touch point velocity tweak) +# I'm currently working on my moonlight-iOS fork, with too much deviations from the original version: +https://github.com/Moonlight-the-Fried-Fish/moonlight-ios-NativeMultiTouchPassthrough + + +# Features of this fork: + +允许调整触点在右分屏或分屏的移速, 对调节米家游戏视角转动的灵敏度尤其有用。 +This is a fork with some manipulation on native multi-touch pointer coordinaties, allows pointer to move faster or slower on specified enhanced touch zone. +Maybe useful for tweaking view rotation sensitivity in some games. +

+ +![image](https://github.com/TrueZhuangJia/moonlight-android-Enhanced-MultiTouch/assets/78474576/3bd8efeb-89ab-477d-b501-22f25cdb8fc6) +![image](https://github.com/TrueZhuangJia/moonlight-android-Enhanced-MultiTouch/assets/78474576/0d58b391-71ef-48be-82f8-6fef1649e2eb) + + +恢复原版moonlight多指敲击屏幕唤醒本地键盘的方式, 同时允许设置敲击手指数量
+Configurable local keyboard toggle:
+![image](https://github.com/TrueZhuangJia/moonlight-android-Enhanced-MultiTouch/assets/78474576/416a2960-f0a7-4245-ac62-d8fb53ec4ca7) +![image](https://github.com/TrueZhuangJia/moonlight-android-Enhanced-MultiTouch/assets/78474576/a0edaf21-a174-448e-832c-da2d171cefea) + + +还有两个的功能, 使用中你可能未必能感觉到有区别:
+And some additional features like flat region to eliminate long press jitter:
+![image](https://github.com/TrueZhuangJia/moonlight-android-Enhanced-MultiTouch/assets/78474576/0594b3ef-e381-4efc-bc2b-db8f209db272) +![image](https://github.com/TrueZhuangJia/moonlight-android-Enhanced-MultiTouch/assets/78474576/98534adc-48ad-4433-8d7c-e60b88c13466) + + +触控与显示同步的话,可能有助理于视角旋转时画面的流畅性。 + + # Moonlight Android [![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/232a8tadrrn8jv0k/branch/master?svg=true)](https://ci.appveyor.com/project/cgutman/moonlight-android/branch/master) diff --git a/app/build.gradle b/app/build.gradle index 26d5f84c1e..67ebed60d8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -82,8 +82,8 @@ android { buildTypes { debug { applicationIdSuffix ".debug" - resValue "string", "app_label", "Moonlight (Debug)" - resValue "string", "app_label_root", "Moonlight (Root Debug)" + resValue "string", "app_label", "Moonlight-搓屏砖家版" + resValue "string", "app_label_root", "Moonlight-搓屏砖家版" minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' diff --git a/app/src/main/java/com/limelight/Game.java b/app/src/main/java/com/limelight/Game.java index 5d214508a3..2bfc474414 100644 --- a/app/src/main/java/com/limelight/Game.java +++ b/app/src/main/java/com/limelight/Game.java @@ -8,6 +8,7 @@ import com.limelight.binding.input.capture.InputCaptureManager; import com.limelight.binding.input.capture.InputCaptureProvider; import com.limelight.binding.input.touch.AbsoluteTouchContext; +import com.limelight.binding.input.touch.NativeTouchContext; import com.limelight.binding.input.touch.RelativeTouchContext; import com.limelight.binding.input.driver.UsbDriverService; import com.limelight.binding.input.evdev.EvdevListener; @@ -23,7 +24,6 @@ import com.limelight.nvstream.http.ComputerDetails; import com.limelight.nvstream.http.NvApp; import com.limelight.nvstream.http.NvHTTP; -import com.limelight.nvstream.input.ControllerPacket; import com.limelight.nvstream.input.KeyboardPacket; import com.limelight.nvstream.input.MouseButtonPacket; import com.limelight.nvstream.jni.MoonBridge; @@ -61,6 +61,7 @@ import android.os.Handler; import android.os.IBinder; import android.util.Rational; +import android.util.Log; import android.view.Display; import android.view.InputDevice; import android.view.KeyCharacterMap; @@ -85,7 +86,11 @@ import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; +// import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; import java.util.Locale; +import java.util.Objects; public class Game extends Activity implements SurfaceHolder.Callback, @@ -96,7 +101,7 @@ public class Game extends Activity implements SurfaceHolder.Callback, // Only 2 touches are supported private final TouchContext[] touchContextMap = new TouchContext[2]; - private long threeFingerDownTime = 0; + private long multiFingerDownTime = 0; private static final int REFERENCE_HORIZ_RES = 1280; private static final int REFERENCE_VERT_RES = 720; @@ -107,7 +112,7 @@ public class Game extends Activity implements SurfaceHolder.Callback, private static final int STYLUS_UP_DEAD_ZONE_DELAY = 150; private static final int STYLUS_UP_DEAD_ZONE_RADIUS = 50; - private static final int THREE_FINGER_TAP_THRESHOLD = 300; + private static final int MULTI_FINGER_TAP_THRESHOLD = 300; private ControllerHandler controllerHandler; private KeyboardTranslator keyboardTranslator; @@ -152,6 +157,7 @@ public class Game extends Activity implements SurfaceHolder.Callback, private WifiManager.WifiLock highPerfWifiLock; private WifiManager.WifiLock lowLatencyWifiLock; + private Map nativeTouchPointerMap = new HashMap<>(); private boolean connectedToUsbDriverService = false; private ServiceConnection usbDriverServiceConnection = new ServiceConnection() { @@ -181,6 +187,7 @@ public void onServiceDisconnected(ComponentName componentName) { public static final String EXTRA_APP_HDR = "HDR"; public static final String EXTRA_SERVER_CERT = "ServerCert"; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -218,6 +225,17 @@ protected void onCreate(Bundle savedInstanceState) { // Read the stream preferences prefConfig = PreferenceConfiguration.readPreferences(this); tombstonePrefs = Game.this.getSharedPreferences("DecoderTombstone", 0); + // Set flat region size for long press jitter elimination. + NativeTouchContext.INTIAL_ZONE_PIXELS = prefConfig.longPressflatRegionPixels; + NativeTouchContext.ENABLE_ENHANCED_TOUCH = prefConfig.enableEnhancedTouch; + if(prefConfig.enhancedTouchOnWhichSide){ + NativeTouchContext.ENHANCED_TOUCH_ON_RIGHT = -1; + }else{ + NativeTouchContext.ENHANCED_TOUCH_ON_RIGHT = 1; + } + NativeTouchContext.ENHANCED_TOUCH_ZONE_DIVIDER = prefConfig.enhanceTouchZoneDivider * 0.01f; + NativeTouchContext.POINTER_VELOCITY_FACTOR = prefConfig.pointerVelocityFactor * 0.01f; + // NativeTouchContext.POINTER_FIXED_X_VELOCITY = prefConfig.pointerFixedXVelocity; // Enter landscape unless we're on a square screen setPreferredOrientationForCurrentDisplay(); @@ -443,7 +461,7 @@ public void notifyCrash(Exception e) { // If the user requested frame pacing using a capped FPS, we will need to change our // desired FPS setting here in accordance with the active display refresh rate. int roundedRefreshRate = Math.round(displayRefreshRate); - int chosenFrameRate = prefConfig.fps; + int chosenFrameRate = prefConfig.fps; //将此处chosenFrameRate赋值为5时, 视频刷新率降低到5,但直接观察远端桌面可知,触控刷新率并未下降,窗口仍可流畅拖动。 if (prefConfig.framePacing == PreferenceConfiguration.FRAME_PACING_CAP_FPS) { if (prefConfig.fps >= roundedRefreshRate) { if (prefConfig.fps > roundedRefreshRate + 3) { @@ -465,7 +483,7 @@ public void notifyCrash(Exception e) { StreamConfiguration config = new StreamConfiguration.Builder() .setResolution(prefConfig.width, prefConfig.height) .setLaunchRefreshRate(prefConfig.fps) - .setRefreshRate(chosenFrameRate) + .setRefreshRate(chosenFrameRate) //将此处chosenFrameRate替换为5时, 视频刷新率降低到5,但直接观察远端桌面可知,触控刷新率并未下降,窗口仍可流畅拖动。 .setApp(app) .setBitrate(prefConfig.bitrate) .setEnableSops(prefConfig.enableSops) @@ -1526,9 +1544,28 @@ private byte getLiTouchTypeFromEvent(MotionEvent event) { } } + private float[] getStreamViewRelativeNormalizedXY(View view, MotionEvent event, int pointerIndex) { - float normalizedX = event.getX(pointerIndex); - float normalizedY = event.getY(pointerIndex); + float normalizedX; + float normalizedY; + if(prefConfig.enableEnhancedTouch){ + // Coords are replaced by NativeTouchContext here. + NativeTouchContext.Pointer pointer = nativeTouchPointerMap.get(event.getPointerId(pointerIndex)); + if(pointer != null) { + float targetCoords[] = pointer.XYCoordSelector(); // decides to passthrough or manipulate coords. + normalizedX = targetCoords[0]; + normalizedY = targetCoords[1]; + } + else{ + normalizedX = 0f; //in this case (pointer == null), pointers are already all up. + normalizedY = 0f; + } + } + else{ + normalizedX = event.getX(pointerIndex); + normalizedY = event.getY(pointerIndex); + } + // For the containing background view, we must subtract the origin // of the StreamView to get video-relative coordinates. @@ -1725,7 +1762,7 @@ else if (event.getActionMasked() == MotionEvent.ACTION_CANCEL) { } private boolean sendTouchEventForPointer(View view, MotionEvent event, byte eventType, int pointerIndex) { - float[] normalizedCoords = getStreamViewRelativeNormalizedXY(view, event, pointerIndex); + float[] normalizedCoords = getStreamViewRelativeNormalizedXY(view, event, pointerIndex); //normalized Coords就是坐标占长或宽的比例,最小0,最大1 float[] normalizedContactArea = getStreamViewNormalizedContactArea(event, pointerIndex); return conn.sendTouchEvent(eventType, event.getPointerId(pointerIndex), normalizedCoords[0], normalizedCoords[1], @@ -1739,12 +1776,21 @@ private boolean trySendTouchEvent(View view, MotionEvent event) { if (eventType < 0) { return false; } - if (event.getActionMasked() == MotionEvent.ACTION_MOVE) { // Move events may impact all active pointers - for (int i = 0; i < event.getPointerCount(); i++) { - if (!sendTouchEventForPointer(view, event, eventType, i)) { - return false; + if(prefConfig.enableEnhancedTouch) { + for (int i = 0; i < event.getPointerCount(); i++) { + Objects.requireNonNull(nativeTouchPointerMap.get(event.getPointerId(i))).updatePointerCoords(event, i); // update pointer coords in the map. + if (!sendTouchEventForPointer(view, event, eventType, i)) { + return false; + } + } + } + else{ + for (int i = 0; i < event.getPointerCount(); i++) { + if (!sendTouchEventForPointer(view, event, eventType, i)) { + return false; + } } } return true; @@ -1756,22 +1802,56 @@ else if (event.getActionMasked() == MotionEvent.ACTION_CANCEL) { MoonBridge.LI_ROT_UNKNOWN) != MoonBridge.LI_ERR_UNSUPPORTED; } else { + switch(event.getActionMasked()) { + case MotionEvent.ACTION_POINTER_DOWN: + multiFingerTapChecker(event); + case MotionEvent.ACTION_DOWN: // first & following finger down. + if(prefConfig.enableEnhancedTouch) { + NativeTouchContext.Pointer pointer = new NativeTouchContext.Pointer(event); //create a Pointer Instance for new touch pointer, put it into the map. + nativeTouchPointerMap.put(pointer.getPointerId(), pointer); + } + break; + case MotionEvent.ACTION_UP: // all fingers up + // toggle keyboard when all fingers lift up, just like how it works in trackpad mode. + if(event.getEventTime() - multiFingerDownTime < MULTI_FINGER_TAP_THRESHOLD) { + toggleKeyboard(); + } + case MotionEvent.ACTION_POINTER_UP: + if(prefConfig.enableEnhancedTouch) { + nativeTouchPointerMap.remove(event.getPointerId(event.getActionIndex())); + } + break; + } // Up, Down, and Hover events are specific to the action index return sendTouchEventForPointer(view, event, eventType, event.getActionIndex()); } } + private void multiFingerTapChecker (MotionEvent event) { + if (event.getPointerCount() == prefConfig.nativeTouchFingersToToggleKeyboard) { + // number of fingers to tap is defined by prefConfig.nativeTouchFingersToToggleKeyboard, configurable from 3 to 10, and -1(disabled) in menu. + + // Cancel the first and second touches to avoid + // erroneous events + // for (TouchContext aTouchContext : touchContextMap) { + // aTouchContext.cancelTouch(); + // } + multiFingerDownTime = event.getEventTime(); + } + } + // Returns true if the event was consumed // NB: View is only present if called from a view callback - private boolean handleMotionEvent(View view, MotionEvent event) { + private boolean handleMotionEvent(View view, MotionEvent event) { //handleMotionEvent, mark mark // Pass through mouse/touch/joystick input if we're not grabbing + if (!grabbedInput) { return false; } int eventSource = event.getSource(); int deviceSources = event.getDevice() != null ? event.getDevice().getSources() : 0; - if ((eventSource & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) { + if ((eventSource & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) { //手柄所属条件 if (controllerHandler.handleMotionEvent(event)) { return true; } @@ -1783,9 +1863,9 @@ else if ((eventSource & InputDevice.SOURCE_CLASS_POINTER) != 0 || (eventSource & InputDevice.SOURCE_CLASS_POSITION) != 0 || eventSource == InputDevice.SOURCE_MOUSE_RELATIVE) { - // This case is for mice and non-finger touch devices + // This case is for mice and non-finger touch devices, 非手指触控功能所属判断条件 if (eventSource == InputDevice.SOURCE_MOUSE || - (eventSource & InputDevice.SOURCE_CLASS_POSITION) != 0 || // SOURCE_TOUCHPAD + (eventSource & InputDevice.SOURCE_CLASS_POSITION) != 0 || // SOURCE_TOUCHPAD虚拟手柄 eventSource == InputDevice.SOURCE_MOUSE_RELATIVE || (event.getPointerCount() >= 1 && (event.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE || @@ -1972,8 +2052,18 @@ else if (event.getActionMasked() == MotionEvent.ACTION_UP || event.getActionMask lastButtonState = buttonState; } // This case is for fingers - else + else //abs touch 和 屏幕虚拟手柄所属的判断条件 { + // TODO: Re-enable native touch when have a better solution for handling + // cancelled touches from Android gestures and 3 finger taps to activate + // the software keyboard. + // 调整一下native touch passthrough的代码顺序 + if (!prefConfig.touchscreenTrackpad && trySendTouchEvent(view, event)) { + // If this host supports touch events and absolute touch is enabled, + // send it directly as a touch event. + return true; + } + if (virtualController != null && (virtualController.getControllerMode() == VirtualController.ControllerMode.MoveButtons || virtualController.getControllerMode() == VirtualController.ControllerMode.ResizeButtons)) { @@ -2002,7 +2092,7 @@ else if (event.getActionMasked() == MotionEvent.ACTION_UP || event.getActionMask if (event.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN && event.getPointerCount() == 3) { // Three fingers down - threeFingerDownTime = event.getEventTime(); + multiFingerDownTime = event.getEventTime(); // Cancel the first and second touches to avoid // erroneous events @@ -2013,10 +2103,12 @@ else if (event.getActionMasked() == MotionEvent.ACTION_UP || event.getActionMask return true; } + // TODO: Re-enable native touch when have a better solution for handling // cancelled touches from Android gestures and 3 finger taps to activate // the software keyboard. - /*if (!prefConfig.touchscreenTrackpad && trySendTouchEvent(view, event)) { + /* + if (!prefConfig.touchscreenTrackpad && trySendTouchEvent(view, event)) { // If this host supports touch events and absolute touch is enabled, // send it directly as a touch event. return true; @@ -2041,8 +2133,9 @@ else if (event.getActionMasked() == MotionEvent.ACTION_UP || event.getActionMask if (event.getPointerCount() == 1 && (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU || (event.getFlags() & MotionEvent.FLAG_CANCELED) == 0)) { // All fingers up - if (event.getEventTime() - threeFingerDownTime < THREE_FINGER_TAP_THRESHOLD) { + if (event.getEventTime() - multiFingerDownTime < MULTI_FINGER_TAP_THRESHOLD) { // This is a 3 finger tap to bring up the keyboard + // multiFingerDownTime, previously threeFingerDowntime, is also used in native-touch for keyboard toggle. toggleKeyboard(); return true; } @@ -2184,10 +2277,12 @@ public boolean onTouch(View view, MotionEvent event) { // Tell the OS not to buffer input events for us // // NB: This is still needed even when we call the newer requestUnbufferedDispatch()! - view.requestUnbufferedDispatch(event); + // Add a configuration to allow view.requestUnbufferedDispatch to be disabled. + if(!prefConfig.syncTouchEventWithDisplay) { + view.requestUnbufferedDispatch(event); + } } - - return handleMotionEvent(view, event); + return handleMotionEvent(view, event); //Y700平板上, onTouch的调用频率为120Hz } @Override diff --git a/app/src/main/java/com/limelight/binding/input/touch/AbsoluteTouchContext.java b/app/src/main/java/com/limelight/binding/input/touch/AbsoluteTouchContext.java index d5fb4708b1..7c2e8a281c 100644 --- a/app/src/main/java/com/limelight/binding/input/touch/AbsoluteTouchContext.java +++ b/app/src/main/java/com/limelight/binding/input/touch/AbsoluteTouchContext.java @@ -3,7 +3,6 @@ import android.os.Handler; import android.os.Looper; import android.view.View; - import com.limelight.nvstream.NvConnection; import com.limelight.nvstream.input.MouseButtonPacket; @@ -195,13 +194,11 @@ public boolean touchMoveEvent(int eventX, int eventY, long eventTime) if (cancelled) { return true; } - if (actionIndex == 0) { if (distanceExceeds(eventX - lastTouchDownX, eventY - lastTouchDownY, LONG_PRESS_DISTANCE_THRESHOLD)) { // Moved too far since touch down. Cancel the long press timer. cancelLongPressTimer(); } - // Ignore motion within the deadzone period after touch down if (confirmedTap || distanceExceeds(eventX - lastTouchDownX, eventY - lastTouchDownY, TOUCH_DOWN_DEAD_ZONE_DISTANCE_THRESHOLD)) { tapConfirmed(); diff --git a/app/src/main/java/com/limelight/binding/input/touch/NativeTouchContext.java b/app/src/main/java/com/limelight/binding/input/touch/NativeTouchContext.java new file mode 100644 index 0000000000..9681b9c578 --- /dev/null +++ b/app/src/main/java/com/limelight/binding/input/touch/NativeTouchContext.java @@ -0,0 +1,325 @@ +package com.limelight.binding.input.touch; + +import android.view.MotionEvent; +import android.util.Log; +import android.content.res.Resources; +import android.util.DisplayMetrics; + +import java.util.ArrayList; +import java.util.Iterator; + +class ScreenUtils { + public static float getScreenWidth() { + DisplayMetrics displayMetrics = Resources.getSystem().getDisplayMetrics(); + return displayMetrics.widthPixels; + } + + public static float getScreenHeight() { + DisplayMetrics displayMetrics = Resources.getSystem().getDisplayMetrics(); + return displayMetrics.heightPixels; + } +} + + +/** + * Pointer oriented class + * + * + * to store additional pointer info updated from Android MotionEvent object + * (stored in NativeTouchContext.Pointer instance). + * Provides some methods to manipulate pointer coordinates (enhanced touch) before sending to Sunshine server, + */ +public class NativeTouchContext { + /** + * Defines a (2*INTIAL_ZONE_PIXELS)^2 square flat region for long press jitter elimination. + * Config read from prefConfig in Game class + */ + public static float INTIAL_ZONE_PIXELS = 0f; + + /** + * Set true to send relative(manipulated) coords to Sunshine server. + * Config read from prefConfig in Game class + */ + public static boolean ENABLE_ENHANCED_TOUCH = true; + + /** + * 1 means enhanced-touch zone on the right side, -1 on the left side. + * Config read from prefConfig in Game class + */ + public static int ENHANCED_TOUCH_ON_RIGHT = 1; + + /** + * Defines where to divide native-touch & enchanced-touch zones, + * < 0.5f means divide from a point on the left, >0.5f means right. + * Config read from prefConfig in Game class + */ + public static float ENHANCED_TOUCH_ZONE_DIVIDER = 0.5f; + + /** + * Factor to scale pointer velocity within enhanced touch zone, + * Config read from prefConfig in Game class + */ + public static float POINTER_VELOCITY_FACTOR = 1.0f; + + /** + * Fixed horizontal velocity (in pixels) within enhanced touch zone + * Config read from prefConfig in Game class + */ + // public static float POINTER_FIXED_X_VELOCITY = 8f; + + /** + * Object to store, update info & manipulate coordinates for each pointer. + * An ArrayList of NativeTouchContext.Pointer instances is created in Game Class for all active pointers. + */ + public static class Pointer{ + /** + * poinerId, not pointerIndex. + * Use pointerId because it's consistent during the whole pointer lifecycle. + */ + private int pointerId; + + /** + * Use MotionEvent.PointerCoords from Android SDK to store coordinates. + */ + private MotionEvent.PointerCoords initialCoords = new MotionEvent.PointerCoords(); //First contact coords of a pointer. + private MotionEvent.PointerCoords latestCoords = new MotionEvent.PointerCoords(); //Latest coords of a pointer updated from MotionEvent provided by Android System. + private MotionEvent.PointerCoords previousCoords = new MotionEvent.PointerCoords(); // previous Coords of a pointer. will be updated in updatePointerCoords(). + private MotionEvent.PointerCoords latestRelativeCoords = new MotionEvent.PointerCoords(); // Coords to replace native ones from Android MotionEvents, for manipulating touch control. + private MotionEvent.PointerCoords previousRelativeCoords = new MotionEvent.PointerCoords(); // Coords to replace native ones from Android MotionEvents, for manipulating touch control. + + /** + * DeltaX & DeltaY between to 2 onTouch() callbacks. + */ + private float velocityX; + private float velocityY; + + + /** + * Flipped to true when pointer moves out of (2*INTIAL_ZONE_PIXELS)^2 square flat region. + */ + private boolean pointerLeftInitialZone = false; + + /** + * Constructor for NativeTouchContext.Pointer. + * Pointer class instantiated in ACTION_DOWN, ACTION_POINTER_DOWN Condition. + */ + public Pointer(MotionEvent event) { + int pointerIndex = event.getActionIndex(); //get the pointerIndex triggers DOWN event. + this.pointerId = event.getPointerId(pointerIndex); // get pointerId + event.getPointerCoords(pointerIndex, this.initialCoords);// Fill in initial coords. + event.getPointerCoords(pointerIndex, this.latestCoords);// Fill in latest coords. + //if(POINTER_VELOCITY_FACTOR != 1.0f){ + this.latestRelativeCoords.x = this.latestCoords.x; + this.latestRelativeCoords.y = this.latestCoords.y; + } + public int getPointerId(){ + return this.pointerId; + } + + /** + * Update native coordinates, relative coordinates & velocity for Pointer instance + */ + public void updatePointerCoords(MotionEvent event, int pointerIndex){ + this.previousCoords.x = this.latestCoords.x; // assign x, y coords to this.previousCoords only. Other attributes can be ignored. + this.previousCoords.y = this.latestCoords.y; + event.getPointerCoords(pointerIndex, this.latestCoords); // update latestCoords from MotionEvent. + + if(POINTER_VELOCITY_FACTOR == 1.0f) { + this.latestRelativeCoords.x = this.latestCoords.x; + this.latestRelativeCoords.y = this.latestCoords.y; + } + else this.updateRelativeCoords(); + + // 固定X速率模式,该模式下仍可用POINTER_VELOCITY_FACTOR调整Y的速率 + // if(POINTER_FIXED_X_VELOCITY != 0f) this.updateRelativeCoordsFixedXVelocity(); + + if(INTIAL_ZONE_PIXELS > 0f) this.flattenLongPressJitter(); + // Log.d("INTIAL_ZONE_PIXELS", ""+INTIAL_ZONE_PIXELS); + } + + /** + * Update relative coordinates with velocity scaled by POINTER_VELOCITY_FACTOR + */ + private void updateRelativeCoords(){ + this.velocityX = this.latestCoords.x - this.previousCoords.x; + this.velocityY = this.latestCoords.y - this.previousCoords.y; + this.previousRelativeCoords.x = this.latestRelativeCoords.x; + this.previousRelativeCoords.y = this.latestRelativeCoords.y; + this.latestRelativeCoords.x = this.previousRelativeCoords.x + this.velocityX * POINTER_VELOCITY_FACTOR; + this.latestRelativeCoords.y = this.previousRelativeCoords.y + this.velocityY * POINTER_VELOCITY_FACTOR; + } + + /** + * Update relative coordinates with fixed X velocity + */ + /* private void updateRelativeCoordsFixedXVelocity(){ + this.velocityX = this.latestCoords.x - this.previousCoords.x; + this.velocityY = this.latestCoords.y - this.previousCoords.y; + this.previousRelativeCoords.x = this.latestRelativeCoords.x; + this.previousRelativeCoords.y = this.latestRelativeCoords.y; + this.latestRelativeCoords.x = this.previousRelativeCoords.x + Math.signum(this.velocityX) * POINTER_FIXED_X_VELOCITY; + this.latestRelativeCoords.y = this.previousRelativeCoords.y + this.velocityY * POINTER_VELOCITY_FACTOR; + } */ + + /** + * Judge whether this pointer leaves (2*INTIAL_ZONE_PIXELS)^2 square flat region + */ + private void checkIfPointerLeaveInitialZone() { + if (!this.pointerLeftInitialZone) { + if (Math.abs(this.latestCoords.x - this.initialCoords.x) > INTIAL_ZONE_PIXELS || Math.abs(this.latestCoords.y - this.initialCoords.y) > INTIAL_ZONE_PIXELS) { + this.pointerLeftInitialZone = true; //Flips pointerLeftInitialZone to true when pointer moves out of flat region. + } + } + } + + /** + * Resets latest coords (both native & relative) to initial coords if pointer doesn't leave flat region. + */ + private void flattenLongPressJitter(){ + this.checkIfPointerLeaveInitialZone(); + // Log.d("INTIAL_ZONE_PIXELS", ""+INTIAL_ZONE_PIXELS); + if(!this.pointerLeftInitialZone){ + this.latestCoords.x = this.initialCoords.x; + this.latestCoords.y = this.initialCoords.y; + this.latestRelativeCoords.x = this.initialCoords.x; + this.latestRelativeCoords.y = this.initialCoords.y; + // Log.d("pointerLeftInitialZone", ""+pointerLeftInitialZone); + } + } + + /** + * Judge whether pointer's coords should be manipulated based on its initial coords (first contact location) + * Only Supports horizontal split (left and right) for now. + */ + private boolean withinEnhancedTouchZone () + { + // float[] normalizedCoords = new float[] {pointerIntialCoords[0]/ScreenUtils.getScreenWidth(), pointerIntialCoords[1]/ScreenUtils.getScreenHeight()}; + float normalizedX = this.initialCoords.x/ScreenUtils.getScreenWidth(); + return normalizedX * ENHANCED_TOUCH_ON_RIGHT > ENHANCED_TOUCH_ZONE_DIVIDER * ENHANCED_TOUCH_ON_RIGHT; + } + + public float[] XYCoordSelector(){ + //to judge whether pointer located on enhanced touch zone by its initial coords. + if (this.withinEnhancedTouchZone()) return new float[] {this.latestRelativeCoords.x,this.latestRelativeCoords.y}; + else return new float[] {this.latestCoords.x,this.latestCoords.y}; + } + + public float getInitialX(){ + return this.initialCoords.x; + } + + public float getPointerNormalizedInitialX(){ + return this.initialCoords.x / ScreenUtils.getScreenWidth(); + } + + public float getInitialY(){ + return this.initialCoords.y; + } + + public float getPointerNormalizedInitialY(){ + return this.initialCoords.y / ScreenUtils.getScreenHeight(); + } + + public float getLatestX(){ + return this.latestCoords.x; + } + + + public float getLatestY(){ + return this.latestCoords.y; + } + public float getLatestRelativeX(){ + return this.latestRelativeCoords.x; + } + + public float getLatestRelativeY(){ + return this.latestRelativeCoords.y; + } + + + public float getPointerNormalizedLatestX(){ + return this.latestCoords.x / ScreenUtils.getScreenWidth(); + } + + public float getPointerNormalizedLatestY(){ + return this.latestCoords.y / ScreenUtils.getScreenHeight(); + } + + public void printPointerInitialCoords(){ + Log.d("Initial Coords","Pointer " + this.pointerId + " Coords: X " + this.getInitialX() + " Y " +this.getInitialY()); + } + public void printPointerLatestCoords(){ + Log.d("Latest Coords","Pointer " + this.pointerId + " Coords: X " + this.getLatestX() + " Y " +this.getLatestY()); + } + public void printPointerCoordSnapshot(){ + Log.d("Pointer " + this.pointerId, " InitialCoords:" + "[" + this.getInitialX() + ", " + this.getInitialY() + "]" + " LatestCoords:" + "[" + this.getLatestX() + ", " + this.getLatestY() + "]"); + } + + + } + + + + + /** + * The Game class defines an ArrayList for all instances all active pointers. + * While iterating pointer info in Game class with pointerIndex in ACTION_MOVE condition, + * this method is called to access additional pointer info from the list by finding a pointerId match, + * and decides whether the pointer's coords should be manipulated. + */ + /* public static float[] selectCoordsForPointer(MotionEvent event, int pointerIndex, ArrayList nativeTouchPointers){ + float selectedX = 0f; + float selectedY = 0f; + for (NativeTouchContext.Pointer pointer : nativeTouchPointers) { + if (event.getPointerId(pointerIndex) == pointer.getPointerId()) { + if(ENABLE_ENHANCED_TOUCH) { + //to judge whether pointer located on enhanced touch zone by its initial coords. + if (isEnhancedTouchZone(new float[] {pointer.getInitialX(), pointer.getInitialY()})) { + selectedX = pointer.getLatestRelativeX(); + selectedY = pointer.getLatestRelativeY(); + } + else{ + selectedX = pointer.getLatestX(); + selectedY = pointer.getLatestY(); + } + } + else{ + selectedX = pointer.getLatestX(); + selectedY = pointer.getLatestY(); + } + break; + } + } + return new float[] {selectedX, selectedY}; + } */ + + + /** + * Safely remove Pointer instance from a List in ACTION_POINTER_UP or ACTION_UP condition + */ + /* public static void safelyRemovePointerFromList(MotionEvent event, ArrayList nativeTouchPointers){ + Iterator iterator = nativeTouchPointers.iterator(); //safely remove pointer handler by iterator. + while (iterator.hasNext()){ + NativeTouchContext.Pointer pointer = iterator.next(); + if (event.getPointerId(event.getActionIndex()) == pointer.getPointerId()) { + iterator.remove(); // immediately break when we get a pointerId match + break; + } + } + } */ + + /** + * Update 1 specific Pointer instance in a List in ACTION_MOVE. + */ + /* public static void updatePointerInList(MotionEvent event,int pointerIndex, ArrayList nativeTouchPointers) { + for (NativeTouchContext.Pointer pointer : nativeTouchPointers) { + if (pointer.getPointerId() == event.getPointerId(pointerIndex)) { + pointer.updatePointerCoords(event, pointerIndex); + // handler.doesPointerLeaveInitialZone(); + // Log.d("NativeTouchCoordHandler", "Pointer Left Initial Zone: " + handler.doesPointerLeaveInitialZone()); + // pointer.printPointerCoordSnapshot(); + break; // immediately break when we get a pointerId match (this method update 1 pointer in the list) + } + } + } */ +} diff --git a/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java b/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java index 8ed01e3610..d7bf540197 100644 --- a/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java +++ b/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java @@ -30,6 +30,12 @@ public enum AnalogStickForScrolling { static final String FPS_PREF_STRING = "list_fps"; static final String BITRATE_PREF_STRING = "seekbar_bitrate_kbps"; private static final String BITRATE_PREF_OLD_STRING = "seekbar_bitrate"; + static final String LONG_PRESS_FLAT_REGION_PIXELS_PREF_STRING = "seekbar_flat_region_pixels"; + static final String SYNC_TOUCH_EVENT_WITH_DISPLAY_PREF_STRING = "checkbox_sync_touch_event_with_display"; + static final String ENABLE_KEYBOARD_TOGGLE_IN_NATIVE_TOUCH = "checkbox_enable_keyboard_toggle_in_native_touch"; + static final String NATIVE_TOUCH_FINGERS_TO_TOGGLE_KEYBOARD_PREF_STRING = "seekbar_keyboard_toggle_fingers_native_touch"; + + private static final String STRETCH_PREF_STRING = "checkbox_stretch_video"; private static final String SOPS_PREF_STRING = "checkbox_enable_sops"; private static final String DISABLE_TOASTS_PREF_STRING = "checkbox_disable_warnings"; @@ -62,6 +68,14 @@ public enum AnalogStickForScrolling { private static final String LATENCY_TOAST_PREF_STRING = "checkbox_enable_post_stream_toast"; private static final String FRAME_PACING_PREF_STRING = "frame_pacing"; private static final String ABSOLUTE_MOUSE_MODE_PREF_STRING = "checkbox_absolute_mouse_mode"; + + private static final String ENABLE_ENHANCED_TOUCH_PREF_STRING = "checkbox_enable_enhanced_touch"; + private static final String ENHANCED_TOUCH_ON_RIGHT_PREF_STRING = "checkbox_enhanced_touch_on_which_side"; + private static final String ENHANCED_TOUCH_ZONE_DIVIDER_PREF_STRING = "enhanced_touch_zone_divider"; + private static final String POINTER_VELOCITY_FACTOR_PREF_STRING = "pointer_velocity_factor"; + // private static final String POINTER_FIXED_X_VELOCITY_PREF_STRING = "fixed_x_velocity"; + + private static final String ENABLE_AUDIO_FX_PREF_STRING = "checkbox_enable_audiofx"; private static final String REDUCE_REFRESH_RATE_PREF_STRING = "checkbox_reduce_refresh_rate"; private static final String FULL_RANGE_PREF_STRING = "checkbox_full_range"; @@ -124,6 +138,17 @@ public enum AnalogStickForScrolling { public int width, height, fps; public int bitrate; + public int longPressflatRegionPixels; //Assigned to NativeTouchContext.INTIAL_ZONE_PIXELS + public boolean syncTouchEventWithDisplay; // if true, view.requestUnbufferedDispatch(event) will be disabled + public boolean enableEnhancedTouch; //Assigned to NativeTouchContext.ENABLE_ENHANCED_TOUCH + public boolean enhancedTouchOnWhichSide; //Assigned to NativeTouchContext.ENHANCED_TOUCH_ON_RIGHT + public int enhanceTouchZoneDivider; //Assigned to NativeTouchContext.ENHANCED_TOUCH_ZONE_DIVIDER + public float pointerVelocityFactor; //Assigned to NativeTouchContext.POINTER_VELOCITY_FACTOR + // public float pointerFixedXVelocity; //Assigned to NativeTouchContext.POINTER_FIXED_X_VELOCITY + public int nativeTouchFingersToToggleKeyboard; // Number of fingers to tap to toggle local on-screen keyboard in native touch mode. + + + public FormatOption videoFormat; public int deadzonePercentage; public int oscOpacity; @@ -548,6 +573,22 @@ else if (str.equals("4K60")) { config.bitrate = getDefaultBitrate(context); } + config.longPressflatRegionPixels = prefs.getInt(LONG_PRESS_FLAT_REGION_PIXELS_PREF_STRING, 0); // define a flat region to suppress coordinates jitter. This is a simulation of iOS behavior since it only send 1 touch event during long press, which feels better in some cases. + config.syncTouchEventWithDisplay = prefs.getBoolean(SYNC_TOUCH_EVENT_WITH_DISPLAY_PREF_STRING, false); // set true to disable "requestUnbufferedDispatch", feels better in some cases. + if(prefs.getBoolean(ENABLE_KEYBOARD_TOGGLE_IN_NATIVE_TOUCH, true)) { + config.nativeTouchFingersToToggleKeyboard = prefs.getInt(NATIVE_TOUCH_FINGERS_TO_TOGGLE_KEYBOARD_PREF_STRING, 3); // least fingers of tap to toggle local keyboard, configurable from 3 to 10 in menu. + } + else{ + config.nativeTouchFingersToToggleKeyboard = -1; // completely disable keyboard toggle in multi-point touch + } + + // Enhance touch settings + config.enableEnhancedTouch = prefs.getBoolean(ENABLE_ENHANCED_TOUCH_PREF_STRING, false); + config.enhancedTouchOnWhichSide = prefs.getBoolean(ENHANCED_TOUCH_ON_RIGHT_PREF_STRING, true); // by default, enhanced touch zone is on the right side. + config.enhanceTouchZoneDivider = prefs.getInt(ENHANCED_TOUCH_ZONE_DIVIDER_PREF_STRING,50); // decides where to divide native touch zone & enhance touch zone by a vertical line. + config.pointerVelocityFactor = prefs.getInt(POINTER_VELOCITY_FACTOR_PREF_STRING,100); // set pointer velocity faster or slower within enhance touch zone, useful in some games for tweaking view rotation sensitivity. + // config.pointerFixedXVelocity = prefs.getInt(POINTER_FIXED_X_VELOCITY_PREF_STRING,0); + String audioConfig = prefs.getString(AUDIO_CONFIG_PREF_STRING, DEFAULT_AUDIO_CONFIG); if (audioConfig.equals("71")) { config.audioConfiguration = MoonBridge.AUDIO_CONFIGURATION_71_SURROUND; diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index c03cd575fa..18152c8c64 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -123,7 +123,21 @@ 为家庭影院系统启用5.1或7.1环绕声 输入设置 将触控屏作为触控板使用 - 如果启用,则将触控屏作为触控板使用。 如果禁止,则触控屏直接控制鼠标光标。 + 如果启用,则将触控屏作为触控板使用。如果禁用,则启用多点触控模式 + + 多点触控长按抖动消除区域 + 全屏生效。像素值越高,抖动消除区域越大。抖动消除区域内,应用只发送触点的初始坐标 + 像素 + + 触控事件与显示刷新同步 + 打开后触控事件上报率与显示刷新率同步,比如显示为60Hz,那么触控与之同步。不一定有用,习惯哪个用哪个。Moonlight原版相当于禁用该项。 + + 多点触控模式下打开本地键盘切换 + \t + 轻敲手指数量 + 轻敲切换本地键盘的最少手指数量。\n仅在多点触控模式生效,触摸板模式3指即可切换本地键盘(不受此配置影响)。 + + 自动检测手柄 禁用此项所有手柄将视为一个手柄 用设备震动模拟游戏震动效果 @@ -138,6 +152,25 @@ 长按开始键将手柄切换为鼠标模式 启用前进后退鼠标键 在一些支持不佳的设备上启用此项可能会使其右键失效 + + 增强型多点触控 + 打开增强型触控 + \t + 反转增强型触控区 + 反转增强型触控区 (增强区默认在右边) + 增强型触控分区位置 + 增强型触控与原生触控的分区位置,小于50%代表在屏幕左边的某个位置分区,大于50%代表分割线在右边 + 增强型触控与原生触控的分区位置,小于50%代表在屏幕左边的某个位置分区,大于50%代表分割线在右边 + % + 触点移速比例 + 设置增强型触控区内的触点移速比例。100% = 禁用,小于100%为缩小,大于100%为放大 + 增强型触控区内的触点移速比例 + % + 设置增强型触控区内触点的水平方向固定移速 + 设置增强型触控区内触点的水平方向固定移速. 0像素 = 禁用,大于0将禁用水平方向的移速比例,改为固定移速 + 触点水平方向固定移速 + 像素 + 反转技能键 为手柄和虚拟手柄调转A/B和X/Y技能键 屏幕控制按钮设置 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9cc2215aaa..6dff4f23fe 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -196,12 +196,44 @@ Input Settings Use the touchscreen as a trackpad - If enabled, the touchscreen acts like a trackpad. If disabled, the touchscreen directly controls the mouse cursor. + If enabled, the touchscreen acts like a trackpad. If disabled, multi-point touch will be enabled. + + Synchronize touch event with display refresh + If enabled, touch event will be synchronized with display refresh rate, like 60Hz. + + Flat region for long press jitter elimination in multi-point touch mode + Works for the whole screen. Increase for larger long-press flat region, within which the app sends initial contact coordinates to server. + pixels + + Enable keyboard toggle in multi-point touch mode + \t + Fingers to tap + Least number of fingers to tap to toggle local on-screen keyboard in multi-point touch mode. \nIn trackpad mode, you always only need to tap 3 or more fingers to toggle local keyboard. + + Remote desktop mouse mode This can make mouse acceleration behave more naturally for remote desktop usage, but it is incompatible with many games. Enable back and forward mouse buttons Enabling this option may break right clicking on some buggy devices + Enhanced Multi-Point Touch + Enable enhanced touch + \t + Reverse enhanced touch zone + Reverse enhanced touch zone to the left (default: on the right side of screen) + Enhanced touch zone divider + Defines where to divide native-touch and enhanced-touch zones, less than 50% means divide from a point on the left, more than 50% means right + Less than 50% means divide from a point on the left, more than 50% means right + % + Pointer velocity factor + Set pointer velocity factor for enhanced touch zone. Set 100% to disable + Set pointer velocity factor for enhanced touch zone. Set 100% to disable + % + Set fixed X (horizontal) velocity + Set fixed X (horizontal) velocity. Set 0 to disable fixed x velocity mode. Larger than 0 means disable horizontal velocity factor to use fixed velocity instead. + Set fixed X (horizontal) velocity in pixels. Set 0 to disable fixed x velocity. + pixels + On-screen Controls Settings Show on-screen controls Show virtual controller overlay on touchscreen diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 5b09082b35..a082656cb0 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -35,7 +35,7 @@ android:entries="@array/video_frame_pacing_names" android:entryValues="@array/video_frame_pacing_values" android:summary="@string/summary_frame_pacing" - android:defaultValue="latency" /> + android:defaultValue="balanced" /> + + + + + + + + + + -