diff --git a/res/values/strings.xml b/res/values/strings.xml
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -48,9 +48,11 @@
Set two finger tap action
Set three finger tap action
Set touchpad sensitivity
+ Set pointer acceleration
mousepad_double_tap_key
mousepad_triple_tap_key
mousepad_sensitivity_key
+ mousepad_acceleration_profile_key
Reverse Scrolling Direction
mousepad_scroll_direction
@@ -61,6 +63,7 @@
right
middle
default
+ medium
- right
- middle
@@ -73,13 +76,29 @@
- Above Default
- Fastest
+
+ - No Acceleration
+ - Weakest
+ - Weaker
+ - Medium
+ - Stronger
+ - Strongest
+
- slowest
- aboveSlowest
- default
- aboveDefault
- fastest
+
+ - noacceleration
+ - weaker
+ - weak
+ - medium
+ - strong
+ - stronger
+
Connected devices
Available devices
Remembered devices
diff --git a/res/xml/mousepadplugin_preferences.xml b/res/xml/mousepadplugin_preferences.xml
--- a/res/xml/mousepadplugin_preferences.xml
+++ b/res/xml/mousepadplugin_preferences.xml
@@ -30,9 +30,18 @@
android:summary="%s"
android:title="@string/mousepad_sensitivity_settings_title" />
+
+
\ No newline at end of file
diff --git a/src/org/kde/kdeconnect/Plugins/MousePadPlugin/MousePadActivity.java b/src/org/kde/kdeconnect/Plugins/MousePadPlugin/MousePadActivity.java
--- a/src/org/kde/kdeconnect/Plugins/MousePadPlugin/MousePadActivity.java
+++ b/src/org/kde/kdeconnect/Plugins/MousePadPlugin/MousePadActivity.java
@@ -60,6 +60,9 @@
private GestureDetector mDetector;
private MousePadGestureDetector mMousePadGestureDetector;
+ private PointerAccelerationProfile mPointerAccelerationProfile;
+
+ private PointerAccelerationProfile.MouseDelta mouseDelta; // to be reused on every touch move event
KeyListenerView keyListenerView;
@@ -111,6 +114,11 @@
String sensitivitySetting = prefs.getString(getString(R.string.mousepad_sensitivity_key),
getString(R.string.mousepad_default_sensitivity));
+ String accelerationProfileName=prefs.getString(getString(R.string.mousepad_acceleration_profile_key),
+ getString(R.string.mousepad_default_acceleration_profile));
+
+ mPointerAccelerationProfile = PointerAccelerationProfileFactory.getProfileWithName(accelerationProfileName);
+
doubleTapAction = ClickType.fromString(doubleTapSetting);
tripleTapAction = ClickType.fromString(tripleTapSetting);
@@ -221,10 +229,16 @@
Device device = service.getDevice(deviceId);
MousePadPlugin mousePadPlugin = device.getPlugin(MousePadPlugin.class);
if (mousePadPlugin == null) return;
- mousePadPlugin.sendMouseDelta(
- (mCurrentX - mPrevX) * displayDpiMultiplier,
- (mCurrentY - mPrevY) * displayDpiMultiplier,
- mCurrentSensitivity);
+
+ float deltaX = (mCurrentX - mPrevX) * displayDpiMultiplier * mCurrentSensitivity;
+ float deltaY = (mCurrentY - mPrevY) * displayDpiMultiplier * mCurrentSensitivity;
+
+ // Run the mouse delta through the pointer acceleration profile
+ mPointerAccelerationProfile.touchMoved(deltaX, deltaY, event.getEventTime());
+ mouseDelta = mPointerAccelerationProfile.commitAcceleratedMouseDelta(mouseDelta);
+
+ mousePadPlugin.sendMouseDelta(mouseDelta.x,mouseDelta.y);
+
mPrevX = mCurrentX;
mPrevY = mCurrentY;
});
diff --git a/src/org/kde/kdeconnect/Plugins/MousePadPlugin/MousePadPlugin.java b/src/org/kde/kdeconnect/Plugins/MousePadPlugin/MousePadPlugin.java
--- a/src/org/kde/kdeconnect/Plugins/MousePadPlugin/MousePadPlugin.java
+++ b/src/org/kde/kdeconnect/Plugins/MousePadPlugin/MousePadPlugin.java
@@ -81,15 +81,11 @@
return context.getString(R.string.open_mousepad);
}
- public void sendMouseDelta(float dx, float dy, float sensitivity) {
+ public void sendMouseDelta(float dx, float dy) {
NetworkPacket np = new NetworkPacket(PACKET_TYPE_MOUSEPAD_REQUEST);
- if (sensitivity <= 0.0001f) {
- sensitivity = 1.0f;
- }
-
- np.set("dx", dx * sensitivity);
- np.set("dy", dy * sensitivity);
+ np.set("dx", dx);
+ np.set("dy", dy);
device.sendPacket(np);
}
diff --git a/src/org/kde/kdeconnect/Plugins/MousePadPlugin/PointerAccelerationProfile.java b/src/org/kde/kdeconnect/Plugins/MousePadPlugin/PointerAccelerationProfile.java
new file mode 100644
--- /dev/null
+++ b/src/org/kde/kdeconnect/Plugins/MousePadPlugin/PointerAccelerationProfile.java
@@ -0,0 +1,29 @@
+package org.kde.kdeconnect.Plugins.MousePadPlugin;
+
+/* Base class for a pointer acceleration profile. */
+public abstract class PointerAccelerationProfile {
+
+ /* Class representing a mouse delta, a pair of floats.*/
+ static class MouseDelta {
+ public float x, y;
+
+ public MouseDelta() {
+ this(0,0);
+ }
+
+ public MouseDelta(float x, float y) {
+ this.x = x;
+ this.y = y;
+ }
+ }
+
+ /* Touch coordinate deltas are fed through this method. */
+ public abstract void touchMoved(float deltaX, float deltaY, long eventTime);
+
+ /* An acceleration profile should 'commit' the processed delta when this method is called.
+ * The value returned here will be directly sent to the desktop client.
+ *
+ * A MouseDelta object can be provided by the caller (or it can be null);
+ * if not null, subclasses should use and return this object, to reduce object allocations.*/
+ public abstract MouseDelta commitAcceleratedMouseDelta(MouseDelta reusedObject);
+}
diff --git a/src/org/kde/kdeconnect/Plugins/MousePadPlugin/PointerAccelerationProfileFactory.java b/src/org/kde/kdeconnect/Plugins/MousePadPlugin/PointerAccelerationProfileFactory.java
new file mode 100644
--- /dev/null
+++ b/src/org/kde/kdeconnect/Plugins/MousePadPlugin/PointerAccelerationProfileFactory.java
@@ -0,0 +1,171 @@
+package org.kde.kdeconnect.Plugins.MousePadPlugin;
+
+
+public class PointerAccelerationProfileFactory {
+
+ /* The simplest profile. Merely adds the mouse deltas without any processing. */
+ private static class DefaultProfile extends PointerAccelerationProfile {
+ float accumulatedX = 0.0f;
+ float accumulatedY = 0.0f;
+
+ @Override
+ public void touchMoved(float deltaX, float deltaY, long eventTime) {
+ accumulatedX += deltaX;
+ accumulatedY += deltaY;
+ }
+
+ @Override
+ public MouseDelta commitAcceleratedMouseDelta(MouseDelta reusedObject) {
+ MouseDelta result;
+ if (reusedObject == null) result = new MouseDelta();
+ else result = reusedObject;
+
+ result.x = accumulatedX;
+ result.y = accumulatedY;
+ accumulatedY = 0;
+ accumulatedX = 0;
+ return result;
+ }
+ }
+
+
+ /* Base class for acceleration profiles that takes touch movement speed into account.
+ * To calculate the speed, a history of 32 touch events are stored
+ * and then later used to calculate the speed. */
+ private static abstract class SpeedBasedAccelerationProfile extends PointerAccelerationProfile {
+ float accumulatedX = 0.0f;
+ float accumulatedY = 0.0f;
+
+ // Higher values will reduce the amount of noise in the speed calculation
+ // but will also increase latency until the acceleration kicks in.
+ // 150ms seemed like a nice middle ground.
+ long freshThreshold = 150;
+
+ private static class TouchDeltaEvent {
+ float x, y;
+ long time;
+ public TouchDeltaEvent(float x, float y, long t) {
+ this.x = x;
+ this.y = y;
+ this.time = t;
+ }
+ }
+
+ private TouchDeltaEvent[] touchEventHistory = new TouchDeltaEvent[32];
+
+ /* add an event to the touchEventHistory array, shifting everything else in the array. */
+ private void addHistory(float deltaX, float deltaY, long eventTime) {
+ for (int i = touchEventHistory.length - 1; i > 0; i--) {
+ touchEventHistory[i] = touchEventHistory[i - 1];
+ }
+ touchEventHistory[0] = new TouchDeltaEvent(deltaX, deltaY, eventTime);
+ }
+
+ @Override
+ public void touchMoved(float deltaX, float deltaY, long eventTime) {
+
+ // To calculate the touch movement speed,
+ // we iterate through the touchEventHistory array,
+ // adding up the distance moved for each event.
+ // Breaks if a touchEventHistory entry is too "stale".
+ float distanceMoved = 0.0f;
+ long deltaT = 0;
+ for (int i = 0; i < touchEventHistory.length; i++) {
+ if (touchEventHistory[i] == null) break;
+ if (eventTime - touchEventHistory[i].time > freshThreshold) break;
+
+ distanceMoved += (float) Math.sqrt(
+ touchEventHistory[i].x * touchEventHistory[i].x
+ + touchEventHistory[i].y * touchEventHistory[i].y);
+ deltaT = eventTime - touchEventHistory[i].time;
+ }
+
+
+ float multiplier;
+
+ if (deltaT == 0) {
+ // Edge case when there are no historical touch data to calculate speed from.
+ multiplier = 0;
+ } else {
+ float speed = distanceMoved / (deltaT / 1000.0f); // units: px/sec
+
+ multiplier = calculateMultiplier(speed);
+ }
+
+ if (multiplier < 0.01f) multiplier = 0.01f;
+
+ accumulatedX += deltaX * multiplier;
+ accumulatedY += deltaY * multiplier;
+
+ addHistory(deltaX, deltaY, eventTime);
+ }
+
+ /* Should be overridden by the child class.
+ * Given the current touch movement speed, this method should return a multiplier
+ * for the touch delta. ( mouse_delta = touch_delta * multiplier ) */
+ abstract float calculateMultiplier(float speed);
+
+ @Override
+ public MouseDelta commitAcceleratedMouseDelta(MouseDelta reusedObject) {
+ MouseDelta result;
+ if (reusedObject == null) result = new MouseDelta();
+ else result = reusedObject;
+
+ /* This makes sure that only the integer components of the deltas are sent,
+ * since the coordinates are converted to integers in the desktop client anyway.
+ * The leftover fractional part is stored and added later; this makes
+ * the cursor move much smoother in slow speeds. */
+ result.x = (int) accumulatedX;
+ result.y = (int) accumulatedY;
+ accumulatedY = accumulatedY % 1.0f;
+ accumulatedX = accumulatedX % 1.0f;
+ return result;
+ }
+ }
+
+ /* Pointer acceleration with mouse_delta = touch_delta * ( touch_speed ^ exponent )
+ * It is similar to x.org server's Polynomial pointer acceleration profile. */
+ private static class PolynomialProfile extends SpeedBasedAccelerationProfile {
+ float exponent;
+
+ public PolynomialProfile(float exponent) {
+ this.exponent = exponent;
+ }
+
+ @Override
+ float calculateMultiplier(float speed) {
+ // The value 600 was chosen arbitrarily.
+ return (float) (Math.pow(speed / 600, exponent));
+ }
+ }
+
+
+ /* Mimics the behavior of xorg's Power profile.
+ * Currently not visible to the user since it is rather hard to control. */
+ private static class PowerProfile extends SpeedBasedAccelerationProfile {
+ @Override
+ float calculateMultiplier(float speed) {
+ return (float) (Math.pow(2, speed / 1000)) - 1;
+ }
+ }
+
+
+ public static PointerAccelerationProfile getProfileWithName(String name) {
+ switch (name) {
+ case "noacceleration":
+ return new DefaultProfile();
+ case "weaker":
+ return new PolynomialProfile(0.25f);
+ case "weak":
+ return new PolynomialProfile(0.5f);
+ case "medium":
+ return new PolynomialProfile(1.0f);
+ case "strong":
+ return new PolynomialProfile(1.5f);
+ case "stronger":
+ return new PolynomialProfile(2.0f);
+ default:
+ return new DefaultProfile();
+ }
+ }
+}