diff --git a/app/src/main/java/com/limelight/Game.java b/app/src/main/java/com/limelight/Game.java
index 331bb7c3..568d07b9 100644
--- a/app/src/main/java/com/limelight/Game.java
+++ b/app/src/main/java/com/limelight/Game.java
@@ -6,9 +6,11 @@ import com.limelight.binding.input.ControllerHandler;
import com.limelight.binding.input.KeyboardTranslator;
import com.limelight.binding.input.capture.InputCaptureManager;
import com.limelight.binding.input.capture.InputCaptureProvider;
-import com.limelight.binding.input.TouchContext;
+import com.limelight.binding.input.touch.AbsoluteTouchContext;
+import com.limelight.binding.input.touch.RelativeTouchContext;
import com.limelight.binding.input.driver.UsbDriverService;
import com.limelight.binding.input.evdev.EvdevListener;
+import com.limelight.binding.input.touch.TouchContext;
import com.limelight.binding.input.virtual_controller.VirtualController;
import com.limelight.binding.video.CrashListener;
import com.limelight.binding.video.MediaCodecDecoderRenderer;
@@ -474,9 +476,14 @@ public class Game extends Activity implements SurfaceHolder.Callback,
// Initialize touch contexts
for (int i = 0; i < touchContextMap.length; i++) {
- touchContextMap[i] = new TouchContext(conn, i,
- REFERENCE_HORIZ_RES, REFERENCE_VERT_RES,
- streamView);
+ if (!prefConfig.touchscreenTrackpad) {
+ touchContextMap[i] = new AbsoluteTouchContext(conn, i, streamView);
+ }
+ else {
+ touchContextMap[i] = new RelativeTouchContext(conn, i,
+ REFERENCE_HORIZ_RES, REFERENCE_VERT_RES,
+ streamView);
+ }
}
// Use sustained performance mode on N+ to ensure consistent
@@ -1332,7 +1339,10 @@ public class Game extends Activity implements SurfaceHolder.Callback,
{
case MotionEvent.ACTION_POINTER_DOWN:
case MotionEvent.ACTION_DOWN:
- context.touchDownEvent(eventX, eventY);
+ for (TouchContext touchContext : touchContextMap) {
+ touchContext.setPointerCount(event.getPointerCount());
+ }
+ context.touchDownEvent(eventX, eventY, true);
break;
case MotionEvent.ACTION_POINTER_UP:
case MotionEvent.ACTION_UP:
@@ -1345,9 +1355,12 @@ public class Game extends Activity implements SurfaceHolder.Callback,
}
}
context.touchUpEvent(eventX, eventY);
+ for (TouchContext touchContext : touchContextMap) {
+ touchContext.setPointerCount(event.getPointerCount() - 1);
+ }
if (actionIndex == 0 && event.getPointerCount() > 1 && !context.isCancelled()) {
// The original secondary touch now becomes primary
- context.touchDownEvent((int)event.getX(1), (int)event.getY(1));
+ context.touchDownEvent((int)event.getX(1), (int)event.getY(1), false);
}
break;
case MotionEvent.ACTION_MOVE:
@@ -1379,6 +1392,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
case MotionEvent.ACTION_CANCEL:
for (TouchContext aTouchContext : touchContextMap) {
aTouchContext.cancelTouch();
+ aTouchContext.setPointerCount(0);
}
break;
default:
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
new file mode 100644
index 00000000..df4b4bb2
--- /dev/null
+++ b/app/src/main/java/com/limelight/binding/input/touch/AbsoluteTouchContext.java
@@ -0,0 +1,272 @@
+package com.limelight.binding.input.touch;
+
+import android.os.SystemClock;
+import android.view.View;
+
+import com.limelight.nvstream.NvConnection;
+import com.limelight.nvstream.input.MouseButtonPacket;
+
+import java.util.Timer;
+import java.util.TimerTask;
+
+public class AbsoluteTouchContext implements TouchContext {
+ private int lastTouchDownX = 0;
+ private int lastTouchDownY = 0;
+ private long lastTouchDownTime = 0;
+ private int lastTouchUpX = 0;
+ private int lastTouchUpY = 0;
+ private long lastTouchUpTime = 0;
+ private int lastTouchLocationX = 0;
+ private int lastTouchLocationY = 0;
+ private boolean cancelled;
+ private boolean confirmedLongPress;
+ private boolean confirmedTap;
+ private Timer longPressTimer;
+ private Timer tapDownTimer;
+ private float accumulatedScrollDelta;
+
+ private final NvConnection conn;
+ private final int actionIndex;
+ private final View targetView;
+
+ private static final int SCROLL_SPEED_DIVISOR = 20;
+
+ private static final int LONG_PRESS_TIME_THRESHOLD = 650;
+ private static final int LONG_PRESS_DISTANCE_THRESHOLD = 30;
+
+ private static final int DOUBLE_TAP_TIME_THRESHOLD = 250;
+ private static final int DOUBLE_TAP_DISTANCE_THRESHOLD = 60;
+
+ private static final int TOUCH_DOWN_DEAD_ZONE_TIME_THRESHOLD = 100;
+ private static final int TOUCH_DOWN_DEAD_ZONE_DISTANCE_THRESHOLD = 20;
+
+ public AbsoluteTouchContext(NvConnection conn, int actionIndex, View view)
+ {
+ this.conn = conn;
+ this.actionIndex = actionIndex;
+ this.targetView = view;
+ }
+
+ @Override
+ public int getActionIndex()
+ {
+ return actionIndex;
+ }
+
+ @Override
+ public boolean touchDownEvent(int eventX, int eventY, boolean isNewFinger)
+ {
+ if (!isNewFinger) {
+ // We don't handle finger transitions for absolute mode
+ return true;
+ }
+
+ lastTouchLocationX = lastTouchDownX = eventX;
+ lastTouchLocationY = lastTouchDownY = eventY;
+ lastTouchDownTime = SystemClock.uptimeMillis();
+ cancelled = confirmedTap = confirmedLongPress = false;
+ accumulatedScrollDelta = 0;
+
+ if (actionIndex == 0) {
+ // Start the timers
+ startTapDownTimer();
+ startLongPressTimer();
+ }
+
+ return true;
+ }
+
+ private boolean distanceExceeds(int deltaX, int deltaY, double limit) {
+ return Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2)) > limit;
+ }
+
+ private void updatePosition(int eventX, int eventY) {
+ // We may get values slightly outside our view region on ACTION_HOVER_ENTER and ACTION_HOVER_EXIT.
+ // Normalize these to the view size. We can't just drop them because we won't always get an event
+ // right at the boundary of the view, so dropping them would result in our cursor never really
+ // reaching the sides of the screen.
+ eventX = Math.min(Math.max(eventX, 0), targetView.getWidth());
+ eventY = Math.min(Math.max(eventY, 0), targetView.getHeight());
+
+ conn.sendMousePosition((short)eventX, (short)eventY, (short)targetView.getWidth(), (short)targetView.getHeight());
+ }
+
+ @Override
+ public void touchUpEvent(int eventX, int eventY)
+ {
+ if (cancelled) {
+ return;
+ }
+
+ if (actionIndex == 0) {
+ // Cancel the timers
+ cancelLongPressTimer();
+ cancelTapDownTimer();
+
+ // Raise the mouse buttons that we currently have down
+ if (confirmedLongPress) {
+ conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_RIGHT);
+ }
+ else if (confirmedTap) {
+ conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_LEFT);
+ }
+ else {
+ // If we get here, this means that the tap completed within the touch down
+ // deadzone time. We'll need to send the touch down and up events now at the
+ // original touch down position.
+ tapConfirmed();
+ try {
+ // FIXME: Sleeping on the main thread sucks
+ Thread.sleep(50);
+ } catch (InterruptedException ignored) {}
+ conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_LEFT);
+ }
+ }
+
+ lastTouchLocationX = lastTouchUpX = eventX;
+ lastTouchLocationY = lastTouchUpY = eventY;
+ lastTouchUpTime = SystemClock.uptimeMillis();
+ }
+
+ private synchronized void startLongPressTimer() {
+ longPressTimer = new Timer(true);
+ longPressTimer.schedule(new TimerTask() {
+ @Override
+ public void run() {
+ synchronized (AbsoluteTouchContext.this) {
+ // Check if someone cancelled us
+ if (longPressTimer == null) {
+ return;
+ }
+
+ // Uncancellable now
+ longPressTimer = null;
+
+ // This timer should have already expired, but cancel it just in case
+ cancelTapDownTimer();
+
+ // Switch from a left click to a right click after a long press
+ confirmedLongPress = true;
+ if (confirmedTap) {
+ conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_LEFT);
+ }
+ conn.sendMouseButtonDown(MouseButtonPacket.BUTTON_RIGHT);
+ }
+ }
+ }, LONG_PRESS_TIME_THRESHOLD);
+ }
+
+ private synchronized void cancelLongPressTimer() {
+ if (longPressTimer != null) {
+ longPressTimer.cancel();
+ longPressTimer = null;
+ }
+ }
+
+ private synchronized void startTapDownTimer() {
+ tapDownTimer = new Timer(true);
+ tapDownTimer.schedule(new TimerTask() {
+ @Override
+ public void run() {
+ synchronized (AbsoluteTouchContext.this) {
+ // Check if someone cancelled us
+ if (tapDownTimer == null) {
+ return;
+ }
+
+ // Uncancellable now
+ tapDownTimer = null;
+
+ // Start our tap
+ tapConfirmed();
+ }
+ }
+ }, TOUCH_DOWN_DEAD_ZONE_TIME_THRESHOLD);
+ }
+
+ private synchronized void cancelTapDownTimer() {
+ if (tapDownTimer != null) {
+ tapDownTimer.cancel();
+ tapDownTimer = null;
+ }
+ }
+
+ private void tapConfirmed() {
+ if (confirmedTap || confirmedLongPress) {
+ return;
+ }
+
+ confirmedTap = true;
+ cancelTapDownTimer();
+
+ // Left button down at original position
+ if (lastTouchDownTime - lastTouchUpTime > DOUBLE_TAP_TIME_THRESHOLD ||
+ distanceExceeds(lastTouchDownX - lastTouchUpX, lastTouchDownY - lastTouchUpY, DOUBLE_TAP_DISTANCE_THRESHOLD)) {
+ // Don't reposition for finger down events within the deadzone. This makes double-clicking easier.
+ updatePosition(lastTouchDownX, lastTouchDownY);
+ }
+ conn.sendMouseButtonDown(MouseButtonPacket.BUTTON_LEFT);
+ }
+
+ @Override
+ public boolean touchMoveEvent(int eventX, int eventY)
+ {
+ 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();
+ updatePosition(eventX, eventY);
+ }
+ }
+ else if (actionIndex == 1) {
+ accumulatedScrollDelta += (eventY - lastTouchLocationY) / (float)SCROLL_SPEED_DIVISOR;
+ if ((short)accumulatedScrollDelta != 0) {
+ conn.sendMouseHighResScroll((short)accumulatedScrollDelta);
+ accumulatedScrollDelta -= (short)accumulatedScrollDelta;
+ }
+ }
+
+ lastTouchLocationX = eventX;
+ lastTouchLocationY = eventY;
+
+ return true;
+ }
+
+ @Override
+ public void cancelTouch() {
+ cancelled = true;
+
+ // Cancel the timers
+ cancelLongPressTimer();
+ cancelTapDownTimer();
+
+ // Raise the mouse buttons
+ if (confirmedLongPress) {
+ conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_RIGHT);
+ }
+ else if (confirmedTap) {
+ conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_LEFT);
+ }
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return cancelled;
+ }
+
+ @Override
+ public void setPointerCount(int pointerCount) {
+ if (actionIndex == 0 && pointerCount > 1) {
+ cancelTouch();
+ }
+ }
+}
diff --git a/app/src/main/java/com/limelight/binding/input/TouchContext.java b/app/src/main/java/com/limelight/binding/input/touch/RelativeTouchContext.java
similarity index 90%
rename from app/src/main/java/com/limelight/binding/input/TouchContext.java
rename to app/src/main/java/com/limelight/binding/input/touch/RelativeTouchContext.java
index 6c7cc59a..be0d8a1b 100644
--- a/app/src/main/java/com/limelight/binding/input/TouchContext.java
+++ b/app/src/main/java/com/limelight/binding/input/touch/RelativeTouchContext.java
@@ -1,5 +1,6 @@
-package com.limelight.binding.input;
+package com.limelight.binding.input.touch;
+import android.os.SystemClock;
import android.view.View;
import com.limelight.nvstream.NvConnection;
@@ -8,7 +9,7 @@ import com.limelight.nvstream.input.MouseButtonPacket;
import java.util.Timer;
import java.util.TimerTask;
-public class TouchContext {
+public class RelativeTouchContext implements TouchContext {
private int lastTouchX = 0;
private int lastTouchY = 0;
private int originalTouchX = 0;
@@ -32,8 +33,8 @@ public class TouchContext {
private static final int TAP_TIME_THRESHOLD = 250;
private static final int DRAG_TIME_THRESHOLD = 650;
- public TouchContext(NvConnection conn, int actionIndex,
- int referenceWidth, int referenceHeight, View view)
+ public RelativeTouchContext(NvConnection conn, int actionIndex,
+ int referenceWidth, int referenceHeight, View view)
{
this.conn = conn;
this.actionIndex = actionIndex;
@@ -42,6 +43,7 @@ public class TouchContext {
this.targetView = view;
}
+ @Override
public int getActionIndex()
{
return actionIndex;
@@ -57,7 +59,7 @@ public class TouchContext {
private boolean isTap()
{
- long timeDelta = System.currentTimeMillis() - originalTouchTime;
+ long timeDelta = SystemClock.uptimeMillis() - originalTouchTime;
return isWithinTapBounds(lastTouchX, lastTouchY) && timeDelta <= TAP_TIME_THRESHOLD;
}
@@ -72,7 +74,8 @@ public class TouchContext {
}
}
- public boolean touchDownEvent(int eventX, int eventY)
+ @Override
+ public boolean touchDownEvent(int eventX, int eventY, boolean isNewFinger)
{
// Get the view dimensions to scale inputs on this touch
xFactor = referenceWidth / (double)targetView.getWidth();
@@ -80,7 +83,7 @@ public class TouchContext {
originalTouchX = lastTouchX = eventX;
originalTouchY = lastTouchY = eventY;
- originalTouchTime = System.currentTimeMillis();
+ originalTouchTime = SystemClock.uptimeMillis();
cancelled = confirmedDrag = confirmedMove = false;
distanceMoved = 0;
@@ -92,6 +95,7 @@ public class TouchContext {
return true;
}
+ @Override
public void touchUpEvent(int eventX, int eventY)
{
if (cancelled) {
@@ -128,7 +132,7 @@ public class TouchContext {
dragTimer.schedule(new TimerTask() {
@Override
public void run() {
- synchronized (TouchContext.this) {
+ synchronized (RelativeTouchContext.this) {
// Check if someone already set move
if (confirmedMove) {
return;
@@ -179,6 +183,7 @@ public class TouchContext {
}
}
+ @Override
public boolean touchMoveEvent(int eventX, int eventY)
{
if (eventX != lastTouchX || eventY != lastTouchY)
@@ -223,6 +228,7 @@ public class TouchContext {
return true;
}
+ @Override
public void cancelTouch() {
cancelled = true;
@@ -235,7 +241,11 @@ public class TouchContext {
}
}
+ @Override
public boolean isCancelled() {
return cancelled;
}
+
+ @Override
+ public void setPointerCount(int pointerCount) {}
}
diff --git a/app/src/main/java/com/limelight/binding/input/touch/TouchContext.java b/app/src/main/java/com/limelight/binding/input/touch/TouchContext.java
new file mode 100644
index 00000000..d08ab3c6
--- /dev/null
+++ b/app/src/main/java/com/limelight/binding/input/touch/TouchContext.java
@@ -0,0 +1,11 @@
+package com.limelight.binding.input.touch;
+
+public interface TouchContext {
+ int getActionIndex();
+ void setPointerCount(int pointerCount);
+ boolean touchDownEvent(int eventX, int eventY, boolean isNewFinger);
+ boolean touchMoveEvent(int eventX, int eventY);
+ void touchUpEvent(int eventX, int eventY);
+ void cancelTouch();
+ boolean isCancelled();
+}
diff --git a/app/src/main/java/com/limelight/nvstream/NvConnection.java b/app/src/main/java/com/limelight/nvstream/NvConnection.java
index 82ec2b05..5b67b0b7 100644
--- a/app/src/main/java/com/limelight/nvstream/NvConnection.java
+++ b/app/src/main/java/com/limelight/nvstream/NvConnection.java
@@ -348,6 +348,12 @@ public class NvConnection {
}
}
+ public void sendMouseHighResScroll(final short scrollAmount) {
+ if (!isMonkey) {
+ MoonBridge.sendMouseHighResScroll(scrollAmount);
+ }
+ }
+
public static String findExternalAddressForMdns(String stunHostname, int stunPort) {
return MoonBridge.findExternalAddressIP4(stunHostname, stunPort);
}
diff --git a/app/src/main/java/com/limelight/nvstream/jni/MoonBridge.java b/app/src/main/java/com/limelight/nvstream/jni/MoonBridge.java
index fb4c041e..71c6807e 100644
--- a/app/src/main/java/com/limelight/nvstream/jni/MoonBridge.java
+++ b/app/src/main/java/com/limelight/nvstream/jni/MoonBridge.java
@@ -261,6 +261,8 @@ public class MoonBridge {
public static native void sendMouseScroll(byte scrollClicks);
+ public static native void sendMouseHighResScroll(short scrollAmount);
+
public static native String getStageName(int stage);
public static native String findExternalAddressIP4(String stunHostName, int stunPort);
diff --git a/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java b/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java
index 4991c9e1..692a9bea 100644
--- a/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java
+++ b/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java
@@ -41,7 +41,8 @@ public class PreferenceConfiguration {
static final String UNLOCK_FPS_STRING = "checkbox_unlock_fps";
private static final String VIBRATE_OSC_PREF_STRING = "checkbox_vibrate_osc";
private static final String VIBRATE_FALLBACK_PREF_STRING = "checkbox_vibrate_fallback";
- private static final String FLIP_FACE_BUTTONS_PERF_STRING = "checkbox_flip_face_buttons";
+ private static final String FLIP_FACE_BUTTONS_PREF_STRING = "checkbox_flip_face_buttons";
+ private static final String TOUCHSCREEN_TRACKPAD_PREF_STRING = "checkbox_touchscreen_trackpad";
static final String DEFAULT_RESOLUTION = "720p";
static final String DEFAULT_FPS = "60";
@@ -69,6 +70,7 @@ public class PreferenceConfiguration {
private static final boolean DEFAULT_VIBRATE_OSC = true;
private static final boolean DEFAULT_VIBRATE_FALLBACK = false;
private static final boolean DEFAULT_FLIP_FACE_BUTTONS = false;
+ private static final boolean DEFAULT_TOUCHSCREEN_TRACKPAD = true;
private static final String DEFAULT_AUDIO_CONFIG = "2"; // Stereo
public static final int FORCE_H265_ON = -1;
@@ -95,6 +97,7 @@ public class PreferenceConfiguration {
public boolean unlockFps;
public boolean vibrateOsc;
public boolean vibrateFallbackToDevice;
+ public boolean touchscreenTrackpad;
public MoonBridge.AudioConfiguration audioConfiguration;
private static int getHeightFromResolutionString(String resString) {
@@ -370,7 +373,8 @@ public class PreferenceConfiguration {
config.unlockFps = prefs.getBoolean(UNLOCK_FPS_STRING, DEFAULT_UNLOCK_FPS);
config.vibrateOsc = prefs.getBoolean(VIBRATE_OSC_PREF_STRING, DEFAULT_VIBRATE_OSC);
config.vibrateFallbackToDevice = prefs.getBoolean(VIBRATE_FALLBACK_PREF_STRING, DEFAULT_VIBRATE_FALLBACK);
- config.flipFaceButtons = prefs.getBoolean(FLIP_FACE_BUTTONS_PERF_STRING, DEFAULT_FLIP_FACE_BUTTONS);
+ config.flipFaceButtons = prefs.getBoolean(FLIP_FACE_BUTTONS_PREF_STRING, DEFAULT_FLIP_FACE_BUTTONS);
+ config.touchscreenTrackpad = prefs.getBoolean(TOUCHSCREEN_TRACKPAD_PREF_STRING, DEFAULT_TOUCHSCREEN_TRACKPAD);
return config;
}
diff --git a/app/src/main/java/com/limelight/preferences/StreamSettings.java b/app/src/main/java/com/limelight/preferences/StreamSettings.java
index 3237f1d2..5f33453d 100644
--- a/app/src/main/java/com/limelight/preferences/StreamSettings.java
+++ b/app/src/main/java/com/limelight/preferences/StreamSettings.java
@@ -141,9 +141,17 @@ public class StreamSettings extends Activity {
// hide on-screen controls category on non touch screen devices
if (!getActivity().getPackageManager().
hasSystemFeature("android.hardware.touchscreen")) {
- PreferenceCategory category =
- (PreferenceCategory) findPreference("category_onscreen_controls");
- screen.removePreference(category);
+ {
+ PreferenceCategory category =
+ (PreferenceCategory) findPreference("category_onscreen_controls");
+ screen.removePreference(category);
+ }
+
+ {
+ PreferenceCategory category =
+ (PreferenceCategory) findPreference("category_input_settings");
+ category.removePreference(findPreference("checkbox_touchscreen_trackpad"));
+ }
}
// Remove PiP mode on devices pre-Oreo
diff --git a/app/src/main/jni/moonlight-core/moonlight-common-c b/app/src/main/jni/moonlight-core/moonlight-common-c
index f596e805..c8faf353 160000
--- a/app/src/main/jni/moonlight-core/moonlight-common-c
+++ b/app/src/main/jni/moonlight-core/moonlight-common-c
@@ -1 +1 @@
-Subproject commit f596e805755cf0b83c14375bc5c9aa182b1766e0
+Subproject commit c8faf3539b0c33b9e5eb8f5cf99c04d307c0e608
diff --git a/app/src/main/jni/moonlight-core/simplejni.c b/app/src/main/jni/moonlight-core/simplejni.c
index f427e341..574b72f4 100644
--- a/app/src/main/jni/moonlight-core/simplejni.c
+++ b/app/src/main/jni/moonlight-core/simplejni.c
@@ -49,6 +49,11 @@ Java_com_limelight_nvstream_jni_MoonBridge_sendMouseScroll(JNIEnv *env, jclass c
LiSendScrollEvent(scrollClicks);
}
+JNIEXPORT void JNICALL
+Java_com_limelight_nvstream_jni_MoonBridge_sendMouseHighResScroll(JNIEnv *env, jclass clazz, jshort scrollAmount) {
+ LiSendHighResScrollEvent(scrollAmount);
+}
+
JNIEXPORT void JNICALL
Java_com_limelight_nvstream_jni_MoonBridge_stopConnection(JNIEnv *env, jclass clazz) {
LiStopConnection();
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 77653c63..8120b14f 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -140,6 +140,8 @@
Enable 5.1 or 7.1 surround sound for home-theater systems
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.
Automatic gamepad presence detection
Unchecking this option forces a gamepad to always be present
Emulate rumble support with vibration
diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml
index c9271a11..8646da56 100644
--- a/app/src/main/res/xml/preferences.xml
+++ b/app/src/main/res/xml/preferences.xml
@@ -51,13 +51,19 @@
android:entryValues="@array/audio_config_values"
android:defaultValue="2" />
-
+
+