[PATCH v2 libinput 22/23] Expose a custom acceleration profile

Peter Hutterer peter.hutterer at who-t.net
Fri Apr 20 04:22:47 UTC 2018


This adds a third profile to the available profiles to map device-specific
speed to an acceleration factor, fully defined by the caller.

There has been a consistent call for different acceleration profiles in
libinput, but very little specifics in what actually needs to be changed.
"faster horses" and whatnot (some notable exceptions in e.g. bug 101139).
Attempts to change the actual acceleration function will likely break things
for others.

This approach opens up the profile itself to a user-specific acceleration
curve. A caller can set an acceleration curve by defining a number of points
on that curve to map input speed to an output factor. That factor is applied
to the input delta.

libinput does relatively little besides mapping the deltas to the
device-specific speed, querying the curve for that speed and applying that
factor. The curve is device-specific, the input speed is in device units/ms.

Signed-off-by: Peter Hutterer <peter.hutterer at who-t.net>
---
Changes to v1:
- renamed to LIBINPUT_CONFIG_ACCEL_PROFILE_DEVICE_SPEED_CURVE so the name
  itself reflects what it does
- documentation fixes

 doc/pointer-acceleration.dox       |  66 +++++++++
 doc/svg/ptraccel-curve-example.svg | 290 +++++++++++++++++++++++++++++++++++++
 meson.build                        |   1 +
 src/evdev.c                        |  29 +++-
 src/filter-custom.c                | 216 +++++++++++++++++++++++++++
 src/filter-private.h               |   2 +
 src/filter.c                       |   9 ++
 src/filter.h                       |  12 ++
 src/libinput-private.h             |   2 +
 src/libinput.c                     |  14 ++
 src/libinput.h                     |  56 +++++++
 src/libinput.sym                   |   4 +
 tools/libinput-debug-events.man    |  10 +-
 tools/libinput-list-devices.c      |   6 +-
 tools/ptraccel-debug.c             |  29 +++-
 tools/shared.c                     |  21 +++
 tools/shared.h                     |   7 +-
 17 files changed, 765 insertions(+), 9 deletions(-)
 create mode 100644 doc/svg/ptraccel-curve-example.svg
 create mode 100644 src/filter-custom.c

diff --git a/doc/pointer-acceleration.dox b/doc/pointer-acceleration.dox
index c120565a..4ed2d231 100644
--- a/doc/pointer-acceleration.dox
+++ b/doc/pointer-acceleration.dox
@@ -157,4 +157,70 @@ Pointer acceleration for relative motion on tablet devices is a flat
 acceleration, with the speed seeting slowing down or speeding up the pointer
 motion by a constant factor. Tablets do not allow for switchable profiles.
 
+ at section ptraccel-device-speed Speed-dependent acceleration curve
+
+When the @ref LIBINPUT_CONFIG_ACCEL_PROFILE_DEVICE_SPEED_CURVE profile is
+selected, it is the caller's responsibility to load an acceleration profile
+into the device that maps the device's movement into an accelerated
+movement.
+
+This profile maps input speed in **device units** to an acceleration
+factor. libinput calculates the device's speed based on the deltas and their
+timestamps and applies the factor provided by the acceleration profile to
+the current delta.
+
+ at note This profile uses data in device units, an acceleration curve loaded
+by the caller only applies to that device and will not behave the same way
+for other devices.
+
+ at dot
+digraph
+{
+	rankdir="LR";
+	node [shape="box";]
+	subgraph cluster0 {
+		history[shape=record,label="<f0> delta(t-1)|<f1> delta(t-2)|<f2> delta(t-3)| <f3> ..."];
+		label = "motion history";
+	}
+	delta[label="delta"];
+	velocity[shape="ellipse"];
+	factor[shape="ellipse"];
+	accel[label="accelerated delta"];
+	curve[label="acceleration curve"];
+
+	delta->velocity;
+	delta->factor;
+	factor->accel;
+	history->velocity;
+
+	velocity->curve;
+	curve->factor;
+}
+ at enddot
+
+For example, assume the current delta is (2, 4) which maps to a current
+movement velocity of 10 units per microsecond. libinput looks up the custom
+acceleration function for 10 which may return 3. The accelerated delta thus
+becomes (6, 12).
+
+The profile itself is a curve defined by a number of points on the curve
+mapping speed to an acceleration factor. Between each two curve points,
+libinput provides linear interpolation, most acceleration profiles will thus
+only need to provide a handful of curve points. The following illustration
+shows the acceleration curve defined for the points (10, 0.25), (20, 0.4)
+and (35, 3.0).
+
+ at image html ptraccel-curve-example.svg "Interpolation of the acceleration curve for three defined points"
+
+As seen in the picture above: given an acceleration factor f(x) and a curve
+defined by the caller for the points
+a, b, and c where a < b < c:
+- if x <= a, f(x) == f(a)
+- if x >= c, f(x) == f(c)
+- if a < x < b: f(x) == the the linear interpolation value between f(a) and f(b)
+- if b < x < c: f(x) == the the linear interpolation value between f(b) and f(c)
+
+The behavior of a a curve is implementation-defined until the caller sets
+curve points.
+
 */
diff --git a/doc/svg/ptraccel-curve-example.svg b/doc/svg/ptraccel-curve-example.svg
new file mode 100644
index 00000000..b098e142
--- /dev/null
+++ b/doc/svg/ptraccel-curve-example.svg
@@ -0,0 +1,290 @@
+<?xml version="1.0" encoding="utf-8"  standalone="no"?>
+<svg
+ width="600" height="480"
+ viewBox="0 0 600 480"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+>
+
+<title>Gnuplot</title>
+<desc>Produced by GNUPLOT 5.0 patchlevel 6 </desc>
+
+<g id="gnuplot_canvas">
+
+<rect x="0" y="0" width="600" height="480" fill="none"/>
+<defs>
+
+	<circle id='gpDot' r='0.5' stroke-width='0.5'/>
+	<path id='gpPt0' stroke-width='0.222' stroke='currentColor' d='M-1,0 h2 M0,-1 v2'/>
+	<path id='gpPt1' stroke-width='0.222' stroke='currentColor' d='M-1,-1 L1,1 M1,-1 L-1,1'/>
+	<path id='gpPt2' stroke-width='0.222' stroke='currentColor' d='M-1,0 L1,0 M0,-1 L0,1 M-1,-1 L1,1 M-1,1 L1,-1'/>
+	<rect id='gpPt3' stroke-width='0.222' stroke='currentColor' x='-1' y='-1' width='2' height='2'/>
+	<rect id='gpPt4' stroke-width='0.222' stroke='currentColor' fill='currentColor' x='-1' y='-1' width='2' height='2'/>
+	<circle id='gpPt5' stroke-width='0.222' stroke='currentColor' cx='0' cy='0' r='1'/>
+	<use xlink:href='#gpPt5' id='gpPt6' fill='currentColor' stroke='none'/>
+	<path id='gpPt7' stroke-width='0.222' stroke='currentColor' d='M0,-1.33 L-1.33,0.67 L1.33,0.67 z'/>
+	<use xlink:href='#gpPt7' id='gpPt8' fill='currentColor' stroke='none'/>
+	<use xlink:href='#gpPt7' id='gpPt9' stroke='currentColor' transform='rotate(180)'/>
+	<use xlink:href='#gpPt9' id='gpPt10' fill='currentColor' stroke='none'/>
+	<use xlink:href='#gpPt3' id='gpPt11' stroke='currentColor' transform='rotate(45)'/>
+	<use xlink:href='#gpPt11' id='gpPt12' fill='currentColor' stroke='none'/>
+	<path id='gpPt13' stroke-width='0.222' stroke='currentColor' d='M0,1.330 L1.265,0.411 L0.782,-1.067 L-0.782,-1.076 L-1.265,0.411 z'/>
+	<use xlink:href='#gpPt13' id='gpPt14' fill='currentColor' stroke='none'/>
+	<filter id='textbox' filterUnits='objectBoundingBox' x='0' y='0' height='1' width='1'>
+	  <feFlood flood-color='white' flood-opacity='1' result='bgnd'/>
+	  <feComposite in='SourceGraphic' in2='bgnd' operator='atop'/>
+	</filter>
+	<filter id='greybox' filterUnits='objectBoundingBox' x='0' y='0' height='1' width='1'>
+	  <feFlood flood-color='lightgrey' flood-opacity='1' result='grey'/>
+	  <feComposite in='SourceGraphic' in2='grey' operator='atop'/>
+	</filter>
+</defs>
+<g fill="none" color="white" stroke="currentColor" stroke-width="1.00" stroke-linecap="butt" stroke-linejoin="miter">
+</g>
+<g fill="none" color="black" stroke="currentColor" stroke-width="1.00" stroke-linecap="butt" stroke-linejoin="miter">
+	<path stroke='black'  d='M71.9,422.4 L80.9,422.4 M575.0,422.4 L566.0,422.4  '/>	<g transform="translate(63.6,426.3)" stroke="none" fill="black" font-family="Arial" font-size="12.00"  text-anchor="end">
+		<text><tspan font-family="Arial" > 0</tspan></text>
+	</g>
+</g>
+<g fill="none" color="black" stroke="currentColor" stroke-width="1.00" stroke-linecap="butt" stroke-linejoin="miter">
+	<path stroke='black'  d='M71.9,364.4 L80.9,364.4 M575.0,364.4 L566.0,364.4  '/>	<g transform="translate(63.6,368.3)" stroke="none" fill="black" font-family="Arial" font-size="12.00"  text-anchor="end">
+		<text><tspan font-family="Arial" > 0.5</tspan></text>
+	</g>
+</g>
+<g fill="none" color="black" stroke="currentColor" stroke-width="1.00" stroke-linecap="butt" stroke-linejoin="miter">
+	<path stroke='black'  d='M71.9,306.5 L80.9,306.5 M575.0,306.5 L566.0,306.5  '/>	<g transform="translate(63.6,310.4)" stroke="none" fill="black" font-family="Arial" font-size="12.00"  text-anchor="end">
+		<text><tspan font-family="Arial" > 1</tspan></text>
+	</g>
+</g>
+<g fill="none" color="black" stroke="currentColor" stroke-width="1.00" stroke-linecap="butt" stroke-linejoin="miter">
+	<path stroke='black'  d='M71.9,248.5 L80.9,248.5 M575.0,248.5 L566.0,248.5  '/>	<g transform="translate(63.6,252.4)" stroke="none" fill="black" font-family="Arial" font-size="12.00"  text-anchor="end">
+		<text><tspan font-family="Arial" > 1.5</tspan></text>
+	</g>
+</g>
+<g fill="none" color="black" stroke="currentColor" stroke-width="1.00" stroke-linecap="butt" stroke-linejoin="miter">
+	<path stroke='black'  d='M71.9,190.6 L80.9,190.6 M575.0,190.6 L566.0,190.6  '/>	<g transform="translate(63.6,194.5)" stroke="none" fill="black" font-family="Arial" font-size="12.00"  text-anchor="end">
+		<text><tspan font-family="Arial" > 2</tspan></text>
+	</g>
+</g>
+<g fill="none" color="black" stroke="currentColor" stroke-width="1.00" stroke-linecap="butt" stroke-linejoin="miter">
+	<path stroke='black'  d='M71.9,132.6 L80.9,132.6 M575.0,132.6 L566.0,132.6  '/>	<g transform="translate(63.6,136.5)" stroke="none" fill="black" font-family="Arial" font-size="12.00"  text-anchor="end">
+		<text><tspan font-family="Arial" > 2.5</tspan></text>
+	</g>
+</g>
+<g fill="none" color="black" stroke="currentColor" stroke-width="1.00" stroke-linecap="butt" stroke-linejoin="miter">
+	<path stroke='black'  d='M71.9,74.7 L80.9,74.7 M575.0,74.7 L566.0,74.7  '/>	<g transform="translate(63.6,78.6)" stroke="none" fill="black" font-family="Arial" font-size="12.00"  text-anchor="end">
+		<text><tspan font-family="Arial" > 3</tspan></text>
+	</g>
+</g>
+<g fill="none" color="black" stroke="currentColor" stroke-width="1.00" stroke-linecap="butt" stroke-linejoin="miter">
+	<path stroke='black'  d='M71.9,16.7 L80.9,16.7 M575.0,16.7 L566.0,16.7  '/>	<g transform="translate(63.6,20.6)" stroke="none" fill="black" font-family="Arial" font-size="12.00"  text-anchor="end">
+		<text><tspan font-family="Arial" > 3.5</tspan></text>
+	</g>
+</g>
+<g fill="none" color="black" stroke="currentColor" stroke-width="1.00" stroke-linecap="butt" stroke-linejoin="miter">
+	<path stroke='black'  d='M71.9,422.4 L71.9,413.4 M71.9,16.7 L71.9,25.7  '/>	<g transform="translate(71.9,444.3)" stroke="none" fill="black" font-family="Arial" font-size="12.00"  text-anchor="middle">
+		<text><tspan font-family="Arial" > 0</tspan></text>
+	</g>
+</g>
+<g fill="none" color="black" stroke="currentColor" stroke-width="1.00" stroke-linecap="butt" stroke-linejoin="miter">
+	<path stroke='black'  d='M134.8,422.4 L134.8,413.4 M134.8,16.7 L134.8,25.7  '/>	<g transform="translate(134.8,444.3)" stroke="none" fill="black" font-family="Arial" font-size="12.00"  text-anchor="middle">
+		<text><tspan font-family="Arial" > 5</tspan></text>
+	</g>
+</g>
+<g fill="none" color="black" stroke="currentColor" stroke-width="1.00" stroke-linecap="butt" stroke-linejoin="miter">
+	<path stroke='black'  d='M197.7,422.4 L197.7,413.4 M197.7,16.7 L197.7,25.7  '/>	<g transform="translate(197.7,444.3)" stroke="none" fill="black" font-family="Arial" font-size="12.00"  text-anchor="middle">
+		<text><tspan font-family="Arial" > 10</tspan></text>
+	</g>
+</g>
+<g fill="none" color="black" stroke="currentColor" stroke-width="1.00" stroke-linecap="butt" stroke-linejoin="miter">
+	<path stroke='black'  d='M260.6,422.4 L260.6,413.4 M260.6,16.7 L260.6,25.7  '/>	<g transform="translate(260.6,444.3)" stroke="none" fill="black" font-family="Arial" font-size="12.00"  text-anchor="middle">
+		<text><tspan font-family="Arial" > 15</tspan></text>
+	</g>
+</g>
+<g fill="none" color="black" stroke="currentColor" stroke-width="1.00" stroke-linecap="butt" stroke-linejoin="miter">
+	<path stroke='black'  d='M323.5,422.4 L323.5,413.4 M323.5,16.7 L323.5,25.7  '/>	<g transform="translate(323.5,444.3)" stroke="none" fill="black" font-family="Arial" font-size="12.00"  text-anchor="middle">
+		<text><tspan font-family="Arial" > 20</tspan></text>
+	</g>
+</g>
+<g fill="none" color="black" stroke="currentColor" stroke-width="1.00" stroke-linecap="butt" stroke-linejoin="miter">
+	<path stroke='black'  d='M386.3,422.4 L386.3,413.4 M386.3,16.7 L386.3,25.7  '/>	<g transform="translate(386.3,444.3)" stroke="none" fill="black" font-family="Arial" font-size="12.00"  text-anchor="middle">
+		<text><tspan font-family="Arial" > 25</tspan></text>
+	</g>
+</g>
+<g fill="none" color="black" stroke="currentColor" stroke-width="1.00" stroke-linecap="butt" stroke-linejoin="miter">
+	<path stroke='black'  d='M449.2,422.4 L449.2,413.4 M449.2,16.7 L449.2,25.7  '/>	<g transform="translate(449.2,444.3)" stroke="none" fill="black" font-family="Arial" font-size="12.00"  text-anchor="middle">
+		<text><tspan font-family="Arial" > 30</tspan></text>
+	</g>
+</g>
+<g fill="none" color="black" stroke="currentColor" stroke-width="1.00" stroke-linecap="butt" stroke-linejoin="miter">
+	<path stroke='black'  d='M512.1,422.4 L512.1,413.4 M512.1,16.7 L512.1,25.7  '/>	<g transform="translate(512.1,444.3)" stroke="none" fill="black" font-family="Arial" font-size="12.00"  text-anchor="middle">
+		<text><tspan font-family="Arial" > 35</tspan></text>
+	</g>
+</g>
+<g fill="none" color="black" stroke="currentColor" stroke-width="1.00" stroke-linecap="butt" stroke-linejoin="miter">
+	<path stroke='black'  d='M575.0,422.4 L575.0,413.4 M575.0,16.7 L575.0,25.7  '/>	<g transform="translate(575.0,444.3)" stroke="none" fill="black" font-family="Arial" font-size="12.00"  text-anchor="middle">
+		<text><tspan font-family="Arial" > 40</tspan></text>
+	</g>
+</g>
+<g fill="none" color="black" stroke="currentColor" stroke-width="1.00" stroke-linecap="butt" stroke-linejoin="miter">
+</g>
+<g fill="none" color="black" stroke="currentColor" stroke-width="1.00" stroke-linecap="butt" stroke-linejoin="miter">
+	<path stroke='black'  d='M71.9,16.7 L71.9,422.4 L575.0,422.4 L575.0,16.7 L71.9,16.7 Z  '/></g>
+<g fill="none" color="black" stroke="currentColor" stroke-width="1.00" stroke-linecap="butt" stroke-linejoin="miter">
+	<g transform="translate(17.0,219.6) rotate(270)" stroke="none" fill="black" font-family="Arial" font-size="12.00"  text-anchor="middle">
+		<text><tspan font-family="Arial" >acceleration factor</tspan></text>
+	</g>
+</g>
+<g fill="none" color="black" stroke="currentColor" stroke-width="1.00" stroke-linecap="butt" stroke-linejoin="miter">
+	<g transform="translate(323.4,471.3)" stroke="none" fill="black" font-family="Arial" font-size="12.00"  text-anchor="middle">
+		<text><tspan font-family="Arial" >speed (device units/ms)</tspan></text>
+	</g>
+</g>
+<g fill="none" color="black" stroke="currentColor" stroke-width="1.00" stroke-linecap="butt" stroke-linejoin="miter">
+</g>
+	<g id="gnuplot_plot_1" ><title>factor</title>
+<g fill="none" color="black" stroke="currentColor" stroke-width="1.00" stroke-linecap="butt" stroke-linejoin="miter">
+	<g transform="translate(507.9,38.6)" stroke="none" fill="black" font-family="Arial" font-size="12.00"  text-anchor="end">
+		<text><tspan font-family="Arial" >factor</tspan></text>
+	</g>
+</g>
+<g fill="none" color="black" stroke="currentColor" stroke-width="1.00" stroke-linecap="butt" stroke-linejoin="miter">
+	<path stroke='rgb(148,   0, 211)'  d='M516.2,34.7 L558.4,34.7 M71.9,393.4 L72.4,393.4 L72.9,393.4 L73.4,393.4 L73.9,393.4 L74.4,393.4
+		L74.9,393.4 L75.4,393.4 L75.9,393.4 L76.4,393.4 L76.9,393.4 L77.3,393.4 L77.8,393.4 L78.3,393.4
+		L78.8,393.4 L79.3,393.4 L79.8,393.4 L80.3,393.4 L80.8,393.4 L81.3,393.4 L81.8,393.4 L82.3,393.4
+		L82.8,393.4 L83.3,393.4 L83.8,393.4 L84.3,393.4 L84.8,393.4 L85.3,393.4 L85.8,393.4 L86.3,393.4
+		L86.8,393.4 L87.3,393.4 L87.7,393.4 L88.2,393.4 L88.7,393.4 L89.2,393.4 L89.7,393.4 L90.2,393.4
+		L90.7,393.4 L91.2,393.4 L91.7,393.4 L92.2,393.4 L92.7,393.4 L93.2,393.4 L93.7,393.4 L94.2,393.4
+		L94.7,393.4 L95.2,393.4 L95.7,393.4 L96.2,393.4 L96.7,393.4 L97.2,393.4 L97.6,393.4 L98.1,393.4
+		L98.6,393.4 L99.1,393.4 L99.6,393.4 L100.1,393.4 L100.6,393.4 L101.1,393.4 L101.6,393.4 L102.1,393.4
+		L102.6,393.4 L103.1,393.4 L103.6,393.4 L104.1,393.4 L104.6,393.4 L105.1,393.4 L105.6,393.4 L106.1,393.4
+		L106.6,393.4 L107.1,393.4 L107.6,393.4 L108.0,393.4 L108.5,393.4 L109.0,393.4 L109.5,393.4 L110.0,393.4
+		L110.5,393.4 L111.0,393.4 L111.5,393.4 L112.0,393.4 L112.5,393.4 L113.0,393.4 L113.5,393.4 L114.0,393.4
+		L114.5,393.4 L115.0,393.4 L115.5,393.4 L116.0,393.4 L116.5,393.4 L117.0,393.4 L117.5,393.4 L118.0,393.4
+		L118.4,393.4 L118.9,393.4 L119.4,393.4 L119.9,393.4 L120.4,393.4 L120.9,393.4 L121.4,393.4 L121.9,393.4
+		L122.4,393.4 L122.9,393.4 L123.4,393.4 L123.9,393.4 L124.4,393.4 L124.9,393.4 L125.4,393.4 L125.9,393.4
+		L126.4,393.4 L126.9,393.4 L127.4,393.4 L127.9,393.4 L128.4,393.4 L128.8,393.4 L129.3,393.4 L129.8,393.4
+		L130.3,393.4 L130.8,393.4 L131.3,393.4 L131.8,393.4 L132.3,393.4 L132.8,393.4 L133.3,393.4 L133.8,393.4
+		L134.3,393.4 L134.8,393.4 L135.3,393.4 L135.8,393.4 L136.3,393.4 L136.8,393.4 L137.3,393.4 L137.8,393.4
+		L138.3,393.4 L138.7,393.4 L139.2,393.4 L139.7,393.4 L140.2,393.4 L140.7,393.4 L141.2,393.4 L141.7,393.4
+		L142.2,393.4 L142.7,393.4 L143.2,393.4 L143.7,393.4 L144.2,393.4 L144.7,393.4 L145.2,393.4 L145.7,393.4
+		L146.2,393.4 L146.7,393.4 L147.2,393.4 L147.7,393.4 L148.2,393.4 L148.7,393.4 L149.1,393.4 L149.6,393.4
+		L150.1,393.4 L150.6,393.4 L151.1,393.4 L151.6,393.4 L152.1,393.4 L152.6,393.4 L153.1,393.4 L153.6,393.4
+		L154.1,393.4 L154.6,393.4 L155.1,393.4 L155.6,393.4 L156.1,393.4 L156.6,393.4 L157.1,393.4 L157.6,393.4
+		L158.1,393.4 L158.6,393.4 L159.1,393.4 L159.5,393.4 L160.0,393.4 L160.5,393.4 L161.0,393.4 L161.5,393.4
+		L162.0,393.4 L162.5,393.4 L163.0,393.4 L163.5,393.4 L164.0,393.4 L164.5,393.4 L165.0,393.4 L165.5,393.4
+		L166.0,393.4 L166.5,393.4 L167.0,393.4 L167.5,393.4 L168.0,393.4 L168.5,393.4 L169.0,393.4 L169.4,393.4
+		L169.9,393.4 L170.4,393.4 L170.9,393.4 L171.4,393.4 L171.9,393.4 L172.4,393.4 L172.9,393.4 L173.4,393.4
+		L173.9,393.4 L174.4,393.4 L174.9,393.4 L175.4,393.4 L175.9,393.4 L176.4,393.4 L176.9,393.4 L177.4,393.4
+		L177.9,393.4 L178.4,393.4 L178.9,393.4 L179.4,393.4 L179.8,393.4 L180.3,393.4 L180.8,393.4 L181.3,393.4
+		L181.8,393.4 L182.3,393.4 L182.8,393.4 L183.3,393.4 L183.8,393.4 L184.3,393.4 L184.8,393.4 L185.3,393.4
+		L185.8,393.4 L186.3,393.4 L186.8,393.4 L187.3,393.4 L187.8,393.4 L188.3,393.4 L188.8,393.4 L189.3,393.4
+		L189.8,393.4 L190.2,393.4 L190.7,393.4 L191.2,393.4 L191.7,393.4 L192.2,393.4 L192.7,393.4 L193.2,393.4
+		L193.7,393.4 L194.2,393.4 L194.7,393.4 L195.2,393.4 L195.7,393.4 L196.2,393.4 L196.7,393.4 L197.2,393.4
+		L197.7,393.4 L198.2,393.4 L198.7,393.3 L199.2,393.2 L199.7,393.1 L200.2,393.1 L200.6,393.0 L201.1,392.9
+		L201.6,392.9 L202.1,392.8 L202.6,392.7 L203.1,392.7 L203.6,392.6 L204.1,392.5 L204.6,392.5 L205.1,392.4
+		L205.6,392.3 L206.1,392.3 L206.6,392.2 L207.1,392.1 L207.6,392.1 L208.1,392.0 L208.6,391.9 L209.1,391.8
+		L209.6,391.8 L210.1,391.7 L210.5,391.6 L211.0,391.6 L211.5,391.5 L212.0,391.4 L212.5,391.4 L213.0,391.3
+		L213.5,391.2 L214.0,391.2 L214.5,391.1 L215.0,391.0 L215.5,391.0 L216.0,390.9 L216.5,390.8 L217.0,390.8
+		L217.5,390.7 L218.0,390.6 L218.5,390.5 L219.0,390.5 L219.5,390.4 L220.0,390.3 L220.5,390.3 L220.9,390.2
+		L221.4,390.1 L221.9,390.1 L222.4,390.0 L222.9,389.9 L223.4,389.9 L223.9,389.8 L224.4,389.7 L224.9,389.7
+		L225.4,389.6 L225.9,389.5 L226.4,389.4 L226.9,389.4 L227.4,389.3 L227.9,389.2 L228.4,389.2 L228.9,389.1
+		L229.4,389.0 L229.9,389.0 L230.4,388.9 L230.9,388.8 L231.3,388.8 L231.8,388.7 L232.3,388.6 L232.8,388.6
+		L233.3,388.5 L233.8,388.4 L234.3,388.4 L234.8,388.3 L235.3,388.2 L235.8,388.1 L236.3,388.1 L236.8,388.0
+		L237.3,388.0 L237.8,387.9 L238.3,387.8 L238.8,387.7 L239.3,387.7 L239.8,387.6 L240.3,387.5 L240.8,387.5
+		L241.3,387.4 L241.7,387.3 L242.2,387.3 L242.7,387.2 L243.2,387.1 L243.7,387.1 L244.2,387.0 L244.7,386.9
+		L245.2,386.8 L245.7,386.8 L246.2,386.7 L246.7,386.6 L247.2,386.6 L247.7,386.5 L248.2,386.4 L248.7,386.4
+		L249.2,386.3 L249.7,386.2 L250.2,386.2 L250.7,386.1 L251.2,386.0 L251.6,386.0 L252.1,385.9 L252.6,385.8
+		L253.1,385.8 L253.6,385.7 L254.1,385.6 L254.6,385.6 L255.1,385.5 L255.6,385.4 L256.1,385.3 L256.6,385.3
+		L257.1,385.2 L257.6,385.1 L258.1,385.1 L258.6,385.0 L259.1,384.9 L259.6,384.9 L260.1,384.8 L260.6,384.7
+		L261.1,384.7 L261.6,384.6 L262.0,384.5 L262.5,384.4 L263.0,384.4 L263.5,384.3 L264.0,384.3 L264.5,384.2
+		L265.0,384.1 L265.5,384.0 L266.0,384.0 L266.5,383.9 L267.0,383.8 L267.5,383.8 L268.0,383.7 L268.5,383.6
+		L269.0,383.6 L269.5,383.5 L270.0,383.4 L270.5,383.4 L271.0,383.3 L271.5,383.2 L272.0,383.2 L272.4,383.1
+		L272.9,383.0 L273.4,382.9 L273.9,382.9 L274.4,382.8 L274.9,382.7 L275.4,382.7 L275.9,382.6 L276.4,382.5
+		L276.9,382.5 L277.4,382.4 L277.9,382.3 L278.4,382.3 L278.9,382.2 L279.4,382.1 L279.9,382.1 L280.4,382.0
+		L280.9,381.9 L281.4,381.9 L281.9,381.8 L282.4,381.7 L282.8,381.6 L283.3,381.6 L283.8,381.5 L284.3,381.4
+		L284.8,381.4 L285.3,381.3 L285.8,381.2 L286.3,381.2 L286.8,381.1 L287.3,381.0 L287.8,381.0 L288.3,380.9
+		L288.8,380.8 L289.3,380.8 L289.8,380.7 L290.3,380.6 L290.8,380.6 L291.3,380.5 L291.8,380.4 L292.3,380.3
+		L292.7,380.3 L293.2,380.2 L293.7,380.1 L294.2,380.1 L294.7,380.0 L295.2,379.9 L295.7,379.9 L296.2,379.8
+		L296.7,379.7 L297.2,379.7 L297.7,379.6 L298.2,379.5 L298.7,379.5 L299.2,379.4 L299.7,379.3 L300.2,379.3
+		L300.7,379.2 L301.2,379.1 L301.7,379.0 L302.2,379.0 L302.7,378.9 L303.1,378.8 L303.6,378.8 L304.1,378.7
+		L304.6,378.6 L305.1,378.6 L305.6,378.5 L306.1,378.4 L306.6,378.4 L307.1,378.3 L307.6,378.2 L308.1,378.2
+		L308.6,378.1 L309.1,378.0 L309.6,377.9 L310.1,377.9 L310.6,377.8 L311.1,377.7 L311.6,377.7 L312.1,377.6
+		L312.6,377.5 L313.1,377.5 L313.5,377.4 L314.0,377.3 L314.5,377.3 L315.0,377.2 L315.5,377.1 L316.0,377.1
+		L316.5,377.0 L317.0,376.9 L317.5,376.9 L318.0,376.8 L318.5,376.7 L319.0,376.6 L319.5,376.6 L320.0,376.5
+		L320.5,376.4 L321.0,376.4 L321.5,376.3 L322.0,376.2 L322.5,376.2 L323.0,376.1 L323.5,376.0 L323.9,375.2
+		L324.4,374.5 L324.9,373.7 L325.4,372.9 L325.9,372.1 L326.4,371.3 L326.9,370.5 L327.4,369.7 L327.9,368.9
+		L328.4,368.1 L328.9,367.3 L329.4,366.5 L329.9,365.8 L330.4,365.0 L330.9,364.2 L331.4,363.4 L331.9,362.6
+		L332.4,361.8 L332.9,361.0 L333.4,360.2 L333.8,359.4 L334.3,358.6 L334.8,357.8 L335.3,357.0 L335.8,356.3
+		L336.3,355.5 L336.8,354.7 L337.3,353.9 L337.8,353.1 L338.3,352.3 L338.8,351.5 L339.3,350.7 L339.8,349.9
+		L340.3,349.1 L340.8,348.4 L341.3,347.6 L341.8,346.8 L342.3,346.0 L342.8,345.2 L343.3,344.4 L343.8,343.6
+		L344.2,342.8 L344.7,342.0 L345.2,341.2 L345.7,340.4 L346.2,339.6 L346.7,338.9 L347.2,338.1 L347.7,337.3
+		L348.2,336.5 L348.7,335.7 L349.2,334.9 L349.7,334.1 L350.2,333.3 L350.7,332.5 L351.2,331.7 L351.7,330.9
+		L352.2,330.2 L352.7,329.4 L353.2,328.6 L353.7,327.8 L354.2,327.0 L354.6,326.2 L355.1,325.4 L355.6,324.6
+		L356.1,323.8 L356.6,323.0 L357.1,322.3 L357.6,321.5 L358.1,320.7 L358.6,319.9 L359.1,319.1 L359.6,318.3
+		L360.1,317.5 L360.6,316.7 L361.1,315.9 L361.6,315.1 L362.1,314.3 L362.6,313.5 L363.1,312.8 L363.6,312.0
+		L364.1,311.2 L364.5,310.4 L365.0,309.6 L365.5,308.8 L366.0,308.0 L366.5,307.2 L367.0,306.4 L367.5,305.6
+		L368.0,304.8 L368.5,304.1 L369.0,303.3 L369.5,302.5 L370.0,301.7 L370.5,300.9 L371.0,300.1 L371.5,299.3
+		L372.0,298.5 L372.5,297.7 L373.0,296.9 L373.5,296.1 L374.0,295.3 L374.5,294.6 L374.9,293.8 L375.4,293.0
+		L375.9,292.2 L376.4,291.4 L376.9,290.6 L377.4,289.8 L377.9,289.0 L378.4,288.2 L378.9,287.4 L379.4,286.7
+		L379.9,285.9 L380.4,285.1 L380.9,284.3 L381.4,283.5 L381.9,282.7 L382.4,281.9 L382.9,281.1 L383.4,280.3
+		L383.9,279.5 L384.4,278.7 L384.9,277.9 L385.3,277.2 L385.8,276.4 L386.3,275.6 L386.8,274.8 L387.3,274.0
+		L387.8,273.2 L388.3,272.4 L388.8,271.6 L389.3,270.8 L389.8,270.0 L390.3,269.2 L390.8,268.5 L391.3,267.7
+		L391.8,266.9 L392.3,266.1 L392.8,265.3 L393.3,264.5 L393.8,263.7 L394.3,262.9 L394.8,262.1 L395.3,261.3
+		L395.7,260.5 L396.2,259.8 L396.7,259.0 L397.2,258.2 L397.7,257.4 L398.2,256.6 L398.7,255.8 L399.2,255.0
+		L399.7,254.2 L400.2,253.4 L400.7,252.6 L401.2,251.8 L401.7,251.1 L402.2,250.3 L402.7,249.5 L403.2,248.7
+		L403.7,247.9 L404.2,247.1 L404.7,246.3 L405.2,245.5 L405.6,244.7 L406.1,243.9 L406.6,243.1 L407.1,242.4
+		L407.6,241.6 L408.1,240.8 L408.6,240.0 L409.1,239.2 L409.6,238.4 L410.1,237.6 L410.6,236.8 L411.1,236.0
+		L411.6,235.2 L412.1,234.4 L412.6,233.7 L413.1,232.9 L413.6,232.1 L414.1,231.3 L414.6,230.5 L415.1,229.7
+		L415.6,228.9 L416.0,228.1 L416.5,227.3 L417.0,226.5 L417.5,225.7 L418.0,225.0 L418.5,224.2 L419.0,223.4
+		L419.5,222.6 L420.0,221.8 L420.5,221.0 L421.0,220.2 L421.5,219.4 L422.0,218.6 L422.5,217.8 L423.0,217.0
+		L423.5,216.2 L424.0,215.5 L424.5,214.7 L425.0,213.9 L425.5,213.1 L426.0,212.3 L426.4,211.5 L426.9,210.7
+		L427.4,209.9 L427.9,209.1 L428.4,208.3 L428.9,207.6 L429.4,206.8 L429.9,206.0 L430.4,205.2 L430.9,204.4
+		L431.4,203.6 L431.9,202.8 L432.4,202.0 L432.9,201.2 L433.4,200.4 L433.9,199.6 L434.4,198.8 L434.9,198.1
+		L435.4,197.3 L435.9,196.5 L436.4,195.7 L436.8,194.9 L437.3,194.1 L437.8,193.3 L438.3,192.5 L438.8,191.7
+		L439.3,190.9 L439.8,190.1 L440.3,189.4 L440.8,188.6 L441.3,187.8 L441.8,187.0 L442.3,186.2 L442.8,185.4
+		L443.3,184.6 L443.8,183.8 L444.3,183.0 L444.8,182.2 L445.3,181.4 L445.8,180.6 L446.3,179.9 L446.7,179.1
+		L447.2,178.3 L447.7,177.5 L448.2,176.7 L448.7,175.9 L449.2,175.1 L449.7,174.3 L450.2,173.5 L450.7,172.7
+		L451.2,172.0 L451.7,171.2 L452.2,170.4 L452.7,169.6 L453.2,168.8 L453.7,168.0 L454.2,167.2 L454.7,166.4
+		L455.2,165.6 L455.7,164.8 L456.2,164.0 L456.7,163.3 L457.1,162.5 L457.6,161.7 L458.1,160.9 L458.6,160.1
+		L459.1,159.3 L459.6,158.5 L460.1,157.7 L460.6,156.9 L461.1,156.1 L461.6,155.3 L462.1,154.5 L462.6,153.8
+		L463.1,153.0 L463.6,152.2 L464.1,151.4 L464.6,150.6 L465.1,149.8 L465.6,149.0 L466.1,148.2 L466.6,147.4
+		L467.1,146.6 L467.5,145.9 L468.0,145.1 L468.5,144.3 L469.0,143.5 L469.5,142.7 L470.0,141.9 L470.5,141.1
+		L471.0,140.3 L471.5,139.5 L472.0,138.7 L472.5,137.9 L473.0,137.1 L473.5,136.4 L474.0,135.6 L474.5,134.8
+		L475.0,134.0 L475.5,133.2 L476.0,132.4 L476.5,131.6 L477.0,130.8 L477.5,130.0 L477.9,129.2 L478.4,128.4
+		L478.9,127.7 L479.4,126.9 L479.9,126.1 L480.4,125.3 L480.9,124.5 L481.4,123.7 L481.9,122.9 L482.4,122.1
+		L482.9,121.3 L483.4,120.5 L483.9,119.7 L484.4,119.0 L484.9,118.2 L485.4,117.4 L485.9,116.6 L486.4,115.8
+		L486.9,115.0 L487.4,114.2 L487.8,113.4 L488.3,112.6 L488.8,111.8 L489.3,111.0 L489.8,110.3 L490.3,109.5
+		L490.8,108.7 L491.3,107.9 L491.8,107.1 L492.3,106.3 L492.8,105.5 L493.3,104.7 L493.8,103.9 L494.3,103.1
+		L494.8,102.3 L495.3,101.5 L495.8,100.8 L496.3,100.0 L496.8,99.2 L497.3,98.4 L497.8,97.6 L498.2,96.8
+		L498.7,96.0 L499.2,95.2 L499.7,94.4 L500.2,93.6 L500.7,92.9 L501.2,92.1 L501.7,91.3 L502.2,90.5
+		L502.7,89.7 L503.2,88.9 L503.7,88.1 L504.2,87.3 L504.7,86.5 L505.2,85.7 L505.7,84.9 L506.2,84.2
+		L506.7,83.4 L507.2,82.6 L507.7,81.8 L508.2,81.0 L508.6,80.2 L509.1,79.4 L509.6,78.6 L510.1,77.8
+		L510.6,77.0 L511.1,76.2 L511.6,75.4 L512.1,74.7 L512.6,74.7 L513.1,74.7 L513.6,74.7 L514.1,74.7
+		L514.6,74.7 L515.1,74.7 L515.6,74.7 L516.1,74.7 L516.6,74.7 L517.1,74.7 L517.6,74.7 L518.1,74.7
+		L518.5,74.7 L519.0,74.7 L519.5,74.7 L520.0,74.7 L520.5,74.7 L521.0,74.7 L521.5,74.7 L522.0,74.7
+		L522.5,74.7 L523.0,74.7 L523.5,74.7 L524.0,74.7 L524.5,74.7 L525.0,74.7 L525.5,74.7 L526.0,74.7
+		L526.5,74.7 L527.0,74.7 L527.5,74.7 L528.0,74.7 L528.5,74.7 L528.9,74.7 L529.4,74.7 L529.9,74.7
+		L530.4,74.7 L530.9,74.7 L531.4,74.7 L531.9,74.7 L532.4,74.7 L532.9,74.7 L533.4,74.7 L533.9,74.7
+		L534.4,74.7 L534.9,74.7 L535.4,74.7 L535.9,74.7 L536.4,74.7 L536.9,74.7 L537.4,74.7 L537.9,74.7
+		L538.4,74.7 L538.9,74.7 L539.3,74.7 L539.8,74.7 L540.3,74.7 L540.8,74.7 L541.3,74.7 L541.8,74.7
+		L542.3,74.7 L542.8,74.7 L543.3,74.7 L543.8,74.7 L544.3,74.7 L544.8,74.7 L545.3,74.7 L545.8,74.7
+		L546.3,74.7 L546.8,74.7 L547.3,74.7 L547.8,74.7 L548.3,74.7 L548.8,74.7 L549.3,74.7 L549.7,74.7
+		L550.2,74.7 L550.7,74.7 L551.2,74.7 L551.7,74.7 L552.2,74.7 L552.7,74.7 L553.2,74.7 L553.7,74.7
+		L554.2,74.7 L554.7,74.7 L555.2,74.7 L555.7,74.7 L556.2,74.7 L556.7,74.7 L557.2,74.7 L557.7,74.7
+		L558.2,74.7 L558.7,74.7 L559.2,74.7 L559.6,74.7 L560.1,74.7 L560.6,74.7 L561.1,74.7 L561.6,74.7
+		L562.1,74.7 L562.6,74.7 L563.1,74.7 L563.6,74.7 L564.1,74.7 L564.6,74.7 L565.1,74.7 L565.6,74.7
+		L566.1,74.7 L566.6,74.7  '/></g>
+	</g>
+<g fill="none" color="white" stroke="rgb(148,   0, 211)" stroke-width="2.00" stroke-linecap="butt" stroke-linejoin="miter">
+</g>
+<g fill="none" color="black" stroke="currentColor" stroke-width="2.00" stroke-linecap="butt" stroke-linejoin="miter">
+</g>
+<g fill="none" color="black" stroke="black" stroke-width="1.00" stroke-linecap="butt" stroke-linejoin="miter">
+</g>
+<g fill="none" color="black" stroke="currentColor" stroke-width="1.00" stroke-linecap="butt" stroke-linejoin="miter">
+	<path stroke='black'  d='M71.9,16.7 L71.9,422.4 L575.0,422.4 L575.0,16.7 L71.9,16.7 Z  '/></g>
+<g fill="none" color="black" stroke="currentColor" stroke-width="1.00" stroke-linecap="butt" stroke-linejoin="miter">
+</g>
+</g>
+</svg>
diff --git a/meson.build b/meson.build
index dfbac572..c2cd0a6f 100644
--- a/meson.build
+++ b/meson.build
@@ -151,6 +151,7 @@ dep_libinput_util = declare_dependency(link_with : libinput_util)
 ############ libfilter.a ############
 src_libfilter = [
 		'src/filter.c',
+		'src/filter-custom.c',
 		'src/filter-flat.c',
 		'src/filter-low-dpi.c',
 		'src/filter-mouse.c',
diff --git a/src/evdev.c b/src/evdev.c
index 2a4682c7..3b65380c 100644
--- a/src/evdev.c
+++ b/src/evdev.c
@@ -943,7 +943,9 @@ evdev_init_accel(struct evdev_device *device,
 {
 	struct motion_filter *filter;
 
-	if (which == LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT)
+	if (which == LIBINPUT_CONFIG_ACCEL_PROFILE_DEVICE_SPEED_CURVE)
+		filter = create_pointer_accelerator_filter_custom_device_speed();
+	else if (which == LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT)
 		filter = create_pointer_accelerator_filter_flat(device->dpi);
 	else if (device->tags & EVDEV_TAG_TRACKPOINT)
 		filter = create_pointer_accelerator_filter_trackpoint(device->trackpoint_range);
@@ -1002,7 +1004,8 @@ evdev_accel_config_get_profiles(struct libinput_device *libinput_device)
 		return LIBINPUT_CONFIG_ACCEL_PROFILE_NONE;
 
 	return LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE |
-		LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT;
+		LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT |
+		LIBINPUT_CONFIG_ACCEL_PROFILE_DEVICE_SPEED_CURVE;
 }
 
 static enum libinput_config_status
@@ -1051,6 +1054,27 @@ evdev_accel_config_get_default_profile(struct libinput_device *libinput_device)
 	return LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE;
 }
 
+static enum libinput_config_status
+evdev_accel_config_set_curve_point(struct libinput_device *libinput_device,
+				   double a,
+				   double fa)
+{
+	struct evdev_device *device = evdev_device(libinput_device);
+	struct motion_filter *filter = device->pointer.filter;
+
+	if (evdev_accel_config_get_profile(libinput_device) !=
+	    LIBINPUT_CONFIG_ACCEL_PROFILE_DEVICE_SPEED_CURVE)
+		return LIBINPUT_CONFIG_STATUS_INVALID;
+
+	if (a < 0 || a > 50000)
+		return LIBINPUT_CONFIG_STATUS_INVALID;
+
+	if (!filter_set_curve_point(filter, a, fa))
+		return LIBINPUT_CONFIG_STATUS_INVALID;
+
+	return LIBINPUT_CONFIG_STATUS_SUCCESS;
+}
+
 void
 evdev_device_init_pointer_acceleration(struct evdev_device *device,
 				       struct motion_filter *filter)
@@ -1068,6 +1092,7 @@ evdev_device_init_pointer_acceleration(struct evdev_device *device,
 		device->pointer.config.set_profile = evdev_accel_config_set_profile;
 		device->pointer.config.get_profile = evdev_accel_config_get_profile;
 		device->pointer.config.get_default_profile = evdev_accel_config_get_default_profile;
+		device->pointer.config.set_curve_point = evdev_accel_config_set_curve_point;
 		device->base.config.accel = &device->pointer.config;
 
 		default_speed = evdev_accel_config_get_default_speed(&device->base);
diff --git a/src/filter-custom.c b/src/filter-custom.c
new file mode 100644
index 00000000..7768aa2c
--- /dev/null
+++ b/src/filter-custom.c
@@ -0,0 +1,216 @@
+/*
+ * Copyright © 2018 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "config.h"
+
+#include <assert.h>
+#include <float.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <limits.h>
+#include <math.h>
+
+#include "filter.h"
+#include "libinput-util.h"
+#include "filter-private.h"
+
+struct acceleration_curve_point {
+	double x, fx;
+};
+
+struct custom_accelerator {
+	struct motion_filter base;
+	struct acceleration_curve_point points[32];
+	size_t npoints;
+
+	double last_velocity;
+	struct pointer_trackers trackers;
+};
+
+double
+custom_accel_profile(struct motion_filter *filter,
+		     void *data,
+		     double speed_in, /* in device units/µs */
+		     uint64_t time)
+{
+	struct custom_accelerator *f =
+		(struct custom_accelerator*)filter;
+	double fx = 1;
+
+	speed_in *= 1000;
+
+	if (f->npoints == 0)
+		return 1.0;
+
+	if (f->points[0].x >= speed_in)
+		return f->points[0].fx;
+
+	for (size_t i = 0; i < f->npoints - 1; i++) {
+		double a, b, fa, fb;
+		double k, d;
+
+		if (f->points[i + 1].x < speed_in)
+			continue;
+
+		/*
+		   We haves points f(i), f(i+1), defining two points on the
+		   curve. linear function in the form y = kx+d:
+
+		   y = kx + d
+
+		   y1 = kx1 + d -> d = y1 - kx1
+		   y2 = kx2 + d -> d = y2 - kx2
+
+		   y1 - kx1 = y2 - kx2
+		   y1 - y2 = kx1 - kx2
+		   k = y1-y2/(x1 - x2)
+
+		 */
+		a  = f->points[i].x;
+		fa = f->points[i].fx;
+		b  = f->points[i+1].x;
+		fb = f->points[i+1].fx;
+
+		k = (fa - fb)/(a - b);
+		d = fa - k * a;
+
+		fx = k * speed_in + d;
+
+		return fx;
+	}
+
+	return f->points[f->npoints - 1].fx;
+}
+
+static struct normalized_coords
+custom_accelerator_filter(struct motion_filter *filter,
+			  const struct device_float_coords *units,
+			  void *data, uint64_t time)
+{
+	struct custom_accelerator *f =
+		(struct custom_accelerator*)filter;
+	struct normalized_coords norm;
+	double velocity; /* units/us in device-native dpi*/
+	double accel_factor;
+
+	trackers_feed(&f->trackers, units, time);
+	velocity = trackers_velocity(&f->trackers, time);
+	accel_factor = calculate_acceleration_simpsons(filter,
+						       custom_accel_profile,
+						       data,
+						       velocity,
+						       f->last_velocity,
+						       time);
+	f->last_velocity = velocity;
+
+	norm.x = accel_factor * units->x;
+	norm.y = accel_factor * units->y;
+
+	return norm;
+}
+
+static bool
+custom_accelerator_set_speed(struct motion_filter *filter,
+			     double speed_adjustment)
+{
+	assert(speed_adjustment >= -1.0 && speed_adjustment <= 1.0);
+
+	/* noop, this function has no effect in the custom interface */
+
+	return true;
+}
+
+static void
+custom_accelerator_destroy(struct motion_filter *filter)
+{
+	struct custom_accelerator *accel_filter =
+		(struct custom_accelerator*)filter;
+
+	trackers_free(&accel_filter->trackers);
+	free(accel_filter);
+}
+
+static bool
+custom_accelerator_set_curve_point(struct motion_filter *filter,
+				   double a, double fa)
+{
+	struct custom_accelerator *f =
+		(struct custom_accelerator*)filter;
+
+	if (f->npoints == ARRAY_LENGTH(f->points))
+		return false;
+
+	if (a < 0 || a > 50000)
+		return false;
+
+	if (f->npoints == 0) {
+		f->points[0].x = a;
+		f->points[0].fx = fa;
+		f->npoints = 1;
+		return true;
+	} else if (f->points[f->npoints - 1].x < a) {
+		f->points[f->npoints].x = a;
+		f->points[f->npoints].fx = fa;
+		f->npoints++;
+		return true;
+	}
+
+	for (size_t i = 0; i < f->npoints; i++) {
+		if (f->points[i].x == a) {
+			f->points[i].fx = fa;
+			break;
+		} else if (f->points[i].x > a) {
+			f->npoints++;
+			for (size_t j = f->npoints - 1; j > i; j--)
+				f->points[j] = f->points[j-1];
+			f->points[i] = (struct acceleration_curve_point){ a, fa };
+			break;
+		}
+	}
+
+	return true;
+}
+
+struct motion_filter_interface accelerator_interface_custom = {
+	.type = LIBINPUT_CONFIG_ACCEL_PROFILE_DEVICE_SPEED_CURVE,
+	.filter = custom_accelerator_filter,
+	.filter_constant = NULL,
+	.restart = NULL,
+	.destroy = custom_accelerator_destroy,
+	.set_speed = custom_accelerator_set_speed,
+	.set_curve_point = custom_accelerator_set_curve_point,
+};
+
+struct motion_filter *
+create_pointer_accelerator_filter_custom_device_speed(void)
+{
+	struct custom_accelerator *filter;
+
+	filter = zalloc(sizeof *filter);
+	trackers_init(&filter->trackers);
+
+	filter->base.interface = &accelerator_interface_custom;
+
+	return &filter->base;
+}
diff --git a/src/filter-private.h b/src/filter-private.h
index fa2fea40..71415dd9 100644
--- a/src/filter-private.h
+++ b/src/filter-private.h
@@ -44,6 +44,8 @@ struct motion_filter_interface {
 	void (*destroy)(struct motion_filter *filter);
 	bool (*set_speed)(struct motion_filter *filter,
 			  double speed_adjustment);
+	bool (*set_curve_point)(struct motion_filter *filter,
+				double a, double fa);
 };
 
 struct motion_filter {
diff --git a/src/filter.c b/src/filter.c
index dd66da70..69b5d140 100644
--- a/src/filter.c
+++ b/src/filter.c
@@ -90,6 +90,15 @@ filter_get_type(struct motion_filter *filter)
 	return filter->interface->type;
 }
 
+bool
+filter_set_curve_point(struct motion_filter *filter, double a, double fa)
+{
+	if (!filter->interface->set_curve_point)
+		return false;
+
+	return filter->interface->set_curve_point(filter, a, fa);
+}
+
 void
 trackers_init(struct pointer_trackers *trackers)
 {
diff --git a/src/filter.h b/src/filter.h
index 506ab123..22d98e09 100644
--- a/src/filter.h
+++ b/src/filter.h
@@ -95,6 +95,9 @@ filter_set_speed(struct motion_filter *filter,
 double
 filter_get_speed(struct motion_filter *filter);
 
+bool
+filter_set_curve_point(struct motion_filter *filter, double a, double fa);
+
 enum libinput_config_accel_profile
 filter_get_type(struct motion_filter *filter);
 
@@ -104,6 +107,9 @@ typedef double (*accel_profile_func_t)(struct motion_filter *filter,
 				       uint64_t time);
 
 /* Pointer acceleration types */
+struct motion_filter *
+create_pointer_accelerator_filter_custom_device_speed(void);
+
 struct motion_filter *
 create_pointer_accelerator_filter_flat(int dpi);
 
@@ -152,6 +158,12 @@ touchpad_lenovo_x230_accel_profile(struct motion_filter *filter,
 				      double speed_in,
 				      uint64_t time);
 double
+custom_accel_profile(struct motion_filter *filter,
+		     void *data,
+		     double speed_in,
+		     uint64_t time);
+
+double
 trackpoint_accel_profile(struct motion_filter *filter,
 			 void *data,
 			 double delta);
diff --git a/src/libinput-private.h b/src/libinput-private.h
index d50154ef..a6938ba6 100644
--- a/src/libinput-private.h
+++ b/src/libinput-private.h
@@ -213,6 +213,8 @@ struct libinput_device_config_accel {
 						   enum libinput_config_accel_profile);
 	enum libinput_config_accel_profile (*get_profile)(struct libinput_device *device);
 	enum libinput_config_accel_profile (*get_default_profile)(struct libinput_device *device);
+	enum libinput_config_status (*set_curve_point)(struct libinput_device *device,
+						       double a, double fa);
 };
 
 struct libinput_device_config_natural_scroll {
diff --git a/src/libinput.c b/src/libinput.c
index 8fb0ba92..be6fce0f 100644
--- a/src/libinput.c
+++ b/src/libinput.c
@@ -3673,6 +3673,19 @@ libinput_device_config_accel_get_default_speed(struct libinput_device *device)
 	return device->config.accel->get_default_speed(device);
 }
 
+LIBINPUT_EXPORT enum libinput_config_status
+libinput_device_config_accel_set_curve_point(
+				struct libinput_device *device,
+				double a, double fa)
+{
+	if (libinput_device_config_accel_get_profile(device) !=
+		    LIBINPUT_CONFIG_ACCEL_PROFILE_DEVICE_SPEED_CURVE) {
+		return LIBINPUT_CONFIG_STATUS_INVALID;
+	}
+
+	return device->config.accel->set_curve_point(device, a, fa);
+}
+
 LIBINPUT_EXPORT uint32_t
 libinput_device_config_accel_get_profiles(struct libinput_device *device)
 {
@@ -3707,6 +3720,7 @@ libinput_device_config_accel_set_profile(struct libinput_device *device,
 	switch (profile) {
 	case LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT:
 	case LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE:
+	case LIBINPUT_CONFIG_ACCEL_PROFILE_DEVICE_SPEED_CURVE:
 		break;
 	default:
 		return LIBINPUT_CONFIG_STATUS_INVALID;
diff --git a/src/libinput.h b/src/libinput.h
index f1a0a2a6..fcb727ff 100644
--- a/src/libinput.h
+++ b/src/libinput.h
@@ -4480,6 +4480,12 @@ libinput_device_config_accel_is_available(struct libinput_device *device);
  * range. libinput picks the semantically closest acceleration step if the
  * requested value does not match a discrete setting.
  *
+ * If the current acceleration profile is @ref
+ * LIBINPUT_CONFIG_ACCEL_PROFILE_DEVICE_SPEED_CURVE, the behavior of the
+ * device will not change but future calls to
+ * libinput_device_config_accel_get_speed() will reflect the updated speed
+ * setting.
+ *
  * @param device The device to configure
  * @param speed The normalized speed, in a range of [-1, 1]
  *
@@ -4528,6 +4534,44 @@ libinput_device_config_accel_get_speed(struct libinput_device *device);
 double
 libinput_device_config_accel_get_default_speed(struct libinput_device *device);
 
+/**
+ * @ingroup config
+ *
+ * Sets a curve point on the custom acceleration function for this device.
+ * This function must be called after setting the type of the acceleration
+ * to @ref LIBINPUT_CONFIG_ACCEL_PROFILE_DEVICE_SPEED_CURVE and sets
+ * exactly one point on the device's acceleration curve.
+ *
+ * This function must be called multiple times to define a full acceleration
+ * curve. libinput uses linear interpolation between each defined curve
+ * point to calculate the appropriate factor. Any speed below or above the
+ * lowest or highest point defined is capped to the factor at the lowest or
+ * highest point, respectively. See @ref ptraccel-device-speed for a
+ * detailed explanation on this behavior.
+ *
+ * The behavior of the acceleration function depends on the type of the
+ * profile:
+ * - @ref LIBINPUT_CONFIG_ACCEL_PROFILE_DEVICE_SPEED_CURVE : the input data
+ *   a is velocity in device units per millisecond, f(a) is a unitless
+ *   factor. This factor is applied to the incoming delta so that a delta
+ *   (x, y) is  accelerated to the delta (f(a) * x, f(a) *y). The velocity
+ *   is calculated by libinput based on the current and previous deltas and
+ *   their timestamps. See @ref ptraccel-device-speed for details.
+ *
+ * @note libinput has a maximum limit for how many curve points may be set
+ * and will quietly drop curve points exceeding this limit. This limit is
+ * not expected to be hit by any reasonable caller.
+ *
+ * Submitting a curve point with the same value as a previous curve point
+ * overwrites that value. There is no facility to remove curve points,
+ * switch the device to a different profile and back again to reset.
+ *
+ * @return 0 on success or nonzero otherwise
+ */
+enum libinput_config_status
+libinput_device_config_accel_set_curve_point(struct libinput_device *device,
+					     double a, double fa);
+
 /**
  * @ingroup config
  */
@@ -4551,6 +4595,11 @@ enum libinput_config_accel_profile {
 	 * on the input speed. This is the default profile for most devices.
 	 */
 	LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE = (1 << 1),
+	/**
+	 * A custom user-provided profile. See
+	 * libinput_acceleration_profile_set_curve_point() for details.
+	 */
+	LIBINPUT_CONFIG_ACCEL_PROFILE_DEVICE_SPEED_CURVE = (1 << 2),
 };
 
 /**
@@ -4572,6 +4621,13 @@ libinput_device_config_accel_get_profiles(struct libinput_device *device);
  * Set the pointer acceleration profile of this pointer device to the given
  * mode.
  *
+ * If the given profile is
+ * @ref LIBINPUT_CONFIG_ACCEL_PROFILE_DEVICE_SPEED_CURVE and it is
+ * different to the current profile, the acceleration curve is reset to an
+ * implementation-defined curve. The caller should call
+ * libinput_device_config_accel_set_curve_point() to
+ * define the curve points of the acceleration profile.
+ *
  * @param device The device to configure
  * @param mode The mode to set the device to.
  *
diff --git a/src/libinput.sym b/src/libinput.sym
index bb283407..f40969bf 100644
--- a/src/libinput.sym
+++ b/src/libinput.sym
@@ -293,3 +293,7 @@ LIBINPUT_1.7 {
 LIBINPUT_1.9 {
 	libinput_device_switch_has_switch;
 } LIBINPUT_1.7;
+
+LIBINPUT_1.11 {
+	libinput_device_config_accel_set_curve_point;
+} LIBINPUT_1.9;
diff --git a/tools/libinput-debug-events.man b/tools/libinput-debug-events.man
index 8a63821e..07674990 100644
--- a/tools/libinput-debug-events.man
+++ b/tools/libinput-debug-events.man
@@ -68,6 +68,11 @@ Enable or disable middle button emulation
 .B \-\-enable\-dwt|\-\-disable\-dwt
 Enable or disable disable-while-typing
 .TP 8
+.B \-\-set\-accel-curve-points="x1:y1;x2:y2"
+Sets the curve points for the \fIcustom-speed\fR acceleration profile. The
+set of curve points is a semicolon-separate lists of key-value pairs, each
+separated by a colon. Each value is a non-negative double.
+.TP 8
 .B \-\-set\-click\-method=[none|clickfinger|buttonareas]
 Set the desired click method
 .TP 8
@@ -77,8 +82,9 @@ Set the desired scroll method
 .B \-\-set\-scroll\-button=BTN_MIDDLE
 Set the button to the given button code
 .TP 8
-.B \-\-set\-profile=[adaptive|flat]
-Set pointer acceleration profile
+.B \-\-set\-profile=[adaptive|flat|custom-speed]
+Set pointer acceleration profile. If the \fIcustom-speed\fR profile is
+selected, use \fB\-\-set-accel-curve-points\fR to specify the curve points.
 .TP 8
 .B \-\-set\-speed=<value>
 Set pointer acceleration speed. The allowed range is [-1, 1].
diff --git a/tools/libinput-list-devices.c b/tools/libinput-list-devices.c
index aa225ca0..060d4b29 100644
--- a/tools/libinput-list-devices.c
+++ b/tools/libinput-list-devices.c
@@ -205,11 +205,13 @@ accel_profiles(struct libinput_device *device)
 
 	profile = libinput_device_config_accel_get_default_profile(device);
 	xasprintf(&str,
-		  "%s%s %s%s",
+		  "%s%s %s%s %s%s",
 		  (profile == LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT) ? "*" : "",
 		  (profiles & LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT) ? "flat" : "",
 		  (profile == LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE) ? "*" : "",
-		  (profiles & LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE) ? "adaptive" : "");
+		  (profiles & LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE) ? "adaptive" : "",
+		  (profile == LIBINPUT_CONFIG_ACCEL_PROFILE_DEVICE_SPEED_CURVE) ? "*" : "",
+		  (profiles & LIBINPUT_CONFIG_ACCEL_PROFILE_DEVICE_SPEED_CURVE) ? "custom-speed" : "");
 
 	return str;
 }
diff --git a/tools/ptraccel-debug.c b/tools/ptraccel-debug.c
index 93be523f..ab7bc13e 100644
--- a/tools/ptraccel-debug.c
+++ b/tools/ptraccel-debug.c
@@ -161,11 +161,12 @@ print_accel_func(struct motion_filter *filter,
 	printf("# set style data lines\n");
 	printf("# plot \"gnuplot.data\" using 1:2 title 'accel factor'\n");
 	printf("#\n");
-	printf("# data: velocity(mm/s) factor velocity(units/us)\n");
+	printf("# data: velocity(mm/s) factor velocity(units/us) velocity(units/ms)\n");
 	for (mmps = 0.0; mmps < 1000.0; mmps += 1) {
 		double units_per_us = mmps_to_upus(mmps, dpi);
+		double units_per_ms = units_per_us * 1000;
 		double result = profile(filter, NULL, units_per_us, 0 /* time */);
-		printf("%.8f\t%.4f\t%.8f\n", mmps, result, units_per_us);
+		printf("%.8f\t%.4f\t%.8f\t%.8f\n", mmps, result, units_per_us, units_per_ms);
 	}
 }
 
@@ -239,6 +240,7 @@ main(int argc, char **argv)
 	const char *filter_type = "linear";
 	accel_profile_func_t profile = NULL;
 	int tp_range_max = 20;
+	const char *curve_points = NULL;
 
 	enum {
 		OPT_HELP = 1,
@@ -250,6 +252,7 @@ main(int argc, char **argv)
 		OPT_DPI,
 		OPT_FILTER,
 		OPT_TRACKPOINT_RANGE,
+		OPT_CURVE_POINTS,
 	};
 
 	while (1) {
@@ -265,6 +268,7 @@ main(int argc, char **argv)
 			{"dpi", 1, 0, OPT_DPI },
 			{"filter", 1, 0, OPT_FILTER },
 			{"trackpoint-range", 1, 0, OPT_TRACKPOINT_RANGE },
+			{"curve-points", 1, 0, OPT_CURVE_POINTS },
 			{0, 0, 0, 0}
 		};
 
@@ -325,6 +329,9 @@ main(int argc, char **argv)
 		case OPT_TRACKPOINT_RANGE:
 			tp_range_max = strtod(optarg, NULL);
 			break;
+		case OPT_CURVE_POINTS:
+			curve_points = optarg;
+			break;
 		default:
 			usage();
 			exit(1);
@@ -347,6 +354,24 @@ main(int argc, char **argv)
 	} else if (streq(filter_type, "trackpoint")) {
 		filter = create_pointer_accelerator_filter_trackpoint(tp_range_max);
 		profile = NULL; /* trackpoint is special */
+	} else if (streq(filter_type, "custom-speed")) {
+		struct key_value_double *points;
+		ssize_t npoints;
+
+		filter = create_pointer_accelerator_filter_custom_device_speed();
+		profile = custom_accel_profile;
+
+		npoints = kv_double_from_string(curve_points, ";", ":", &points);
+		if (npoints <= 0)
+			return 1;
+
+		for (ssize_t idx = 0; idx < npoints; idx++){
+			filter_set_curve_point(filter,
+					       points[idx].key,
+					       points[idx].value);
+		}
+
+		free(points);
 	} else {
 		fprintf(stderr, "Invalid filter type %s\n", filter_type);
 		return 1;
diff --git a/tools/shared.c b/tools/shared.c
index c1ce6473..85c0d739 100644
--- a/tools/shared.c
+++ b/tools/shared.c
@@ -218,6 +218,17 @@ tools_parse_option(int option,
 			 "%s",
 			 optarg);
 		break;
+	case OPT_CURVE_POINTS:
+		if (!optarg)
+			return 1;
+
+		options->ncurve_points = kv_double_from_string(
+						optarg,
+						";", ":",
+						&options->curve_points);
+		if (options->ncurve_points < 0)
+			return 1;
+		break;
 	}
 
 	return 0;
@@ -386,6 +397,16 @@ tools_device_apply_config(struct libinput_device *device,
 		libinput_device_config_send_events_set_mode(device,
 					    LIBINPUT_CONFIG_SEND_EVENTS_DISABLED);
 	}
+
+	if (libinput_device_config_accel_get_profile(device) ==
+		    LIBINPUT_CONFIG_ACCEL_PROFILE_DEVICE_SPEED_CURVE) {
+		for (ssize_t idx = 0; idx < options->ncurve_points; idx++) {
+			double x = options->curve_points[idx].key,
+			       fx = options->curve_points[idx].value;
+
+			libinput_device_config_accel_set_curve_point(device, x, fx);
+		}
+	}
 }
 
 static char*
diff --git a/tools/shared.h b/tools/shared.h
index 55e15409..dc61b5b7 100644
--- a/tools/shared.h
+++ b/tools/shared.h
@@ -50,6 +50,7 @@ enum configuration_options {
 	OPT_SPEED,
 	OPT_PROFILE,
 	OPT_DISABLE_SENDEVENTS,
+	OPT_CURVE_POINTS,
 };
 
 #define CONFIGURATION_OPTIONS \
@@ -73,7 +74,8 @@ enum configuration_options {
 	{ "set-scroll-button",         required_argument, 0, OPT_SCROLL_BUTTON }, \
 	{ "set-profile",               required_argument, 0, OPT_PROFILE }, \
 	{ "set-tap-map",               required_argument, 0, OPT_TAP_MAP }, \
-	{ "set-speed",                 required_argument, 0, OPT_SPEED }
+	{ "set-speed",                 required_argument, 0, OPT_SPEED }, \
+	{ "set-accel-curve-points",    required_argument, 0, OPT_CURVE_POINTS }
 
 enum tools_backend {
 	BACKEND_DEVICE,
@@ -95,6 +97,9 @@ struct tools_options {
 	int dwt;
 	enum libinput_config_accel_profile profile;
 	char disable_pattern[64];
+
+	struct key_value_double *curve_points;
+	ssize_t ncurve_points;
 };
 
 void tools_init_options(struct tools_options *options);
-- 
2.14.3



More information about the wayland-devel mailing list