diff --git a/app/src/main/java/com/limelight/Game.java b/app/src/main/java/com/limelight/Game.java
index 5d214508a3..a98b0e9ffb 100644
--- a/app/src/main/java/com/limelight/Game.java
+++ b/app/src/main/java/com/limelight/Game.java
@@ -1826,18 +1826,41 @@ else if (event.getAction() == MotionEvent.ACTION_UP) {
// dealing with a stylus without hover support, our position might be
// significantly different than before.
if (inputCaptureProvider.eventHasRelativeMouseAxes(event)) {
- // Send the deltas straight from the motion event
- short deltaX = (short)inputCaptureProvider.getRelativeAxisX(event);
- short deltaY = (short)inputCaptureProvider.getRelativeAxisY(event);
-
- if (deltaX != 0 || deltaY != 0) {
- if (prefConfig.absoluteMouseMode) {
- // NB: view may be null, but we can unconditionally use streamView because we don't need to adjust
- // relative axis deltas for the position of the streamView within the parent's coordinate system.
- conn.sendMouseMoveAsMousePosition(deltaX, deltaY, (short)streamView.getWidth(), (short)streamView.getHeight());
+ float rawX = inputCaptureProvider.getRelativeAxisX(event);
+ float rawY = inputCaptureProvider.getRelativeAxisY(event);
+
+ // Some keyboard trackpads (e.g. Samsung Galaxy Tab S11 Ultra Pro Keyboard)
+ // report AXIS_RELATIVE_X/Y rotated 90° clockwise: the driver sends physical-Y
+ // as AXIS_RELATIVE_X and physical-X as AXIS_RELATIVE_Y. Correcting means
+ // reading Y as X and negating the new Y.
+ if (prefConfig.rotateTouchpadAxes && eventSource == InputDevice.SOURCE_TOUCHPAD) {
+ float tmp = rawX;
+ rawX = rawY;
+ rawY = -tmp;
+ }
+
+ // Samsung keyboard trackpads send two-finger scroll as ACTION_MOVE with
+ // getPointerCount() == 2, using the same REL_X/REL_Y axes as cursor movement
+ // rather than ACTION_SCROLL with AXIS_VSCROLL/HSCROLL.
+ if (eventSource == InputDevice.SOURCE_TOUCHPAD && event.getPointerCount() == 2) {
+ if (rawX != 0 || rawY != 0) {
+ int scrollDir = prefConfig.naturalScroll ? 1 : -1;
+ conn.sendMouseHighResScroll((short)(rawY * 10 * scrollDir));
+ conn.sendMouseHighResHScroll((short)(-rawX * 10 * scrollDir));
}
- else {
- conn.sendMouseMove(deltaX, deltaY);
+ } else {
+ short deltaX = (short) rawX;
+ short deltaY = (short) rawY;
+
+ if (deltaX != 0 || deltaY != 0) {
+ if (prefConfig.absoluteMouseMode) {
+ // NB: view may be null, but we can unconditionally use streamView because we don't need to adjust
+ // relative axis deltas for the position of the streamView within the parent's coordinate system.
+ conn.sendMouseMoveAsMousePosition(deltaX, deltaY, (short)streamView.getWidth(), (short)streamView.getHeight());
+ }
+ else {
+ conn.sendMouseMove(deltaX, deltaY);
+ }
}
}
}
@@ -1878,8 +1901,9 @@ else if (view != null) {
if (event.getActionMasked() == MotionEvent.ACTION_SCROLL) {
// Send the vertical scroll packet
- conn.sendMouseHighResScroll((short)(event.getAxisValue(MotionEvent.AXIS_VSCROLL) * 120));
- conn.sendMouseHighResHScroll((short)(event.getAxisValue(MotionEvent.AXIS_HSCROLL) * 120));
+ int scrollDir = prefConfig.naturalScroll ? -1 : 1;
+ conn.sendMouseHighResScroll((short)(event.getAxisValue(MotionEvent.AXIS_VSCROLL) * 120 * scrollDir));
+ conn.sendMouseHighResHScroll((short)(event.getAxisValue(MotionEvent.AXIS_HSCROLL) * 120 * scrollDir));
}
if ((changedButtons & MotionEvent.BUTTON_PRIMARY) != 0) {
diff --git a/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java b/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java
index 8ed01e3610..920e62cbac 100644
--- a/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java
+++ b/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java
@@ -68,6 +68,8 @@ public enum AnalogStickForScrolling {
private static final String GAMEPAD_TOUCHPAD_AS_MOUSE_PREF_STRING = "checkbox_gamepad_touchpad_as_mouse";
private static final String GAMEPAD_MOTION_SENSORS_PREF_STRING = "checkbox_gamepad_motion_sensors";
private static final String GAMEPAD_MOTION_FALLBACK_PREF_STRING = "checkbox_gamepad_motion_fallback";
+ private static final String ROTATE_TOUCHPAD_AXES_PREF_STRING = "checkbox_rotate_touchpad_axes";
+ private static final String NATURAL_SCROLL_PREF_STRING = "checkbox_natural_scroll";
static final String DEFAULT_RESOLUTION = "1280x720";
static final String DEFAULT_FPS = "60";
@@ -108,6 +110,8 @@ public enum AnalogStickForScrolling {
private static final boolean DEFAULT_GAMEPAD_TOUCHPAD_AS_MOUSE = false;
private static final boolean DEFAULT_GAMEPAD_MOTION_SENSORS = true;
private static final boolean DEFAULT_GAMEPAD_MOTION_FALLBACK = false;
+ private static final boolean DEFAULT_ROTATE_TOUCHPAD_AXES = false;
+ private static final boolean DEFAULT_NATURAL_SCROLL = false;
public static final int FRAME_PACING_MIN_LATENCY = 0;
public static final int FRAME_PACING_BALANCED = 1;
@@ -155,6 +159,8 @@ public enum AnalogStickForScrolling {
public boolean gamepadMotionSensors;
public boolean gamepadTouchpadAsMouse;
public boolean gamepadMotionSensorsFallbackToDevice;
+ public boolean rotateTouchpadAxes;
+ public boolean naturalScroll;
public static boolean isNativeResolution(int width, int height) {
// It's not a native resolution if it matches an existing resolution option
@@ -601,6 +607,8 @@ else if (audioConfig.equals("51")) {
config.gamepadTouchpadAsMouse = prefs.getBoolean(GAMEPAD_TOUCHPAD_AS_MOUSE_PREF_STRING, DEFAULT_GAMEPAD_TOUCHPAD_AS_MOUSE);
config.gamepadMotionSensors = prefs.getBoolean(GAMEPAD_MOTION_SENSORS_PREF_STRING, DEFAULT_GAMEPAD_MOTION_SENSORS);
config.gamepadMotionSensorsFallbackToDevice = prefs.getBoolean(GAMEPAD_MOTION_FALLBACK_PREF_STRING, DEFAULT_GAMEPAD_MOTION_FALLBACK);
+ config.rotateTouchpadAxes = prefs.getBoolean(ROTATE_TOUCHPAD_AXES_PREF_STRING, DEFAULT_ROTATE_TOUCHPAD_AXES);
+ config.naturalScroll = prefs.getBoolean(NATURAL_SCROLL_PREF_STRING, DEFAULT_NATURAL_SCROLL);
return config;
}
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 9cc2215aaa..c0f4ed4d1f 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -199,6 +199,10 @@
If enabled, the touchscreen acts like a trackpad. If disabled, the touchscreen directly controls the mouse cursor.
Remote desktop mouse mode
This can make mouse acceleration behave more naturally for remote desktop usage, but it is incompatible with many games.
+ Rotate external trackpad axes
+ Enable if your keyboard trackpad moves the cursor in the wrong direction (e.g. up/down movement causes left/right movement). Fixes devices like the Samsung Galaxy Tab Pro Keyboard.
+ Invert scroll direction
+ Reverses the scroll direction for trackpad and mouse scroll wheel.
Enable back and forward mouse buttons
Enabling this option may break right clicking on some buggy devices
diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml
index 5b09082b35..3bf0ab9cbf 100644
--- a/app/src/main/res/xml/preferences.xml
+++ b/app/src/main/res/xml/preferences.xml
@@ -144,6 +144,16 @@
android:title="@string/title_checkbox_absolute_mouse_mode"
android:summary="@string/summary_checkbox_absolute_mouse_mode"
android:defaultValue="false" />
+
+