Who-T

Thursday, May 10, 2018

In the first three parts, I covered the X server and synaptics pointer
acceleration curves and how libinput compares to the X server pointer
acceleration curve. In this post, I will compare libinput to the synaptics
acceleration curve.

Comparison of synaptics and libinput

libinput has multiple different pointer acceleration curves, depending on the device.
In this post, I will only consider the one used for touchpads. So let's
compare the synaptics curve with the libinput curve at the default
configurations:

These two graphs show that libinput is both very different and similar. Both
curves have an acceleration factor less than 1 for the majority of speeds,
they both decelerate the touchpad more than accelerating it.
synaptics has two factors it sticks to and a short curve,
libinput has a short deceleration curve and its plateau is the same or lower
than synaptics for the most part. Once the threshold is hit at around 250
mm/s, libinput's acceleration keeps increasing until it hits a maximum much later.

So, anything under ~20mm/s, libinput should be the same as synaptics
(ignoring the <7mm/s deceleration). For anything less than 250mm/s, libinput should be
slower. I say "should be" because that is not actually the case, synaptics
is slower so I suspect the server scaling slows down synaptics even further.
Hacking around in the libinput code, I found that moving libinput's baseline
to 0.2 matches the synaptics cursor's speed. However, AFAIK that scaling
depends on the screen size, so your mileage may vary.

Comparing configuration settings

Let's overlay the libinput speed toggles. In
Part
2 we've seen the synaptics toggles and they're open-ended, so it's a bit
hard to pick a specific set to go with to compare. I'll be using the same
combined configuration options from the diagram there.

There isn't much I can talk about here in direct comparison, the curves
are quite different and the synaptics curves vary greatly with the
configuration options (even though the shape remains the same).

Analysis

It's fairly obvious that the acceleration profiles are very different once
depart from the default settings. Most notable,
only libinput's slowest speed setting matches the 0.2 speed
that is the synaptics default setting. In other words, if your touchpad
is too fast compared to synaptics, it may not be possible to slow it down
sufficiently. Likewise, even at the fastest speed, the baseline is well
below the synaptics baseline for e.g. 0.6 [1], so if your touchpad is too slow,
you may not be able to speed it up sufficiently (at least for low speeds).
That problem won't exist for the maximum acceleration factor, the main
question here is simply whether they are too high. Answer: I don't know.

So the base speed of the touchpad in libinput needs a higher range, that's
IMO a definitive bug that I need to work on. The rest... I don't know. Let's
see how we go.

[1] A configuration I found suggested in some forum when googling for MinSpeed, so let's assume there's at least one person out there using it.

In Part 1 and
Part
2 I showed the X server acceleration code as used by the evdev and
synaptics drivers. In this part, I'll show how it compares against libinput.

Comparison to libinput

libinput has multiple different pointer acceleration curves, depending on the device.
In this post, I will only consider the default one used for mice. A discussion of the
touchpad acceleration curve comes later. So, back to the graph of the simple profile.
Let's overlay this with the
libinput
pointer acceleration curve:

Turns out the pointer acceleration curve, mostly modeled after the xserver
behaviour roughly matches the xserver behaviour. Note that
libinput normalizes to 1000dpi (provided MOUSE_DPI is set correctly)
and thus the curves only match this way for 1000dpi devices.

libinput's deceleration is slightly different but I doubt it is really
noticeable. The plateau of no acceleration is virtually identical, i.e. at
slow speeds libinput moves like the xserver's pointer does. Likewise for
speeds above ~33mm/s, libinput and the server accelerate by the same amount.
The actual curve is slightly different. It is a linear curve (I doubt that's
noticeable) and it doesn't have that jump in it. The xserver acceleration
maxes out at roughly 20mm/s. The only difference in acceleration is for the
range of 10mm/s to 33mm/s.

30mm/s is still a relatively slow movement (just move your mouse by 30mm
within a second, it doesn't feel fast). This means that for all but slow
movements, the current server and libinput acceleration provides but a flat
acceleration at whatever the maximum acceleration is set to.

Comparison of configuration options

The biggest difference libinput has to the X server is that it exposes a
single knob of normalised continuous configuration (-1.0 == slowest, 1.0 ==
fastest). It relies on settings like MOUSE_DPI to provide enough
information to map a device into that normalised range.

Let's look at the libinput speed settings and their effect on the
acceleration profile (libinput 1.10.x).

libinput's speed setting is a combination of changing thresholds and accel
at the same time. The faster you go, the sooner acceleration applies and the
higher the maximum acceleration is. For very slow speeds, libinput provides
deceleration. Noticeable here though is that the baseline speed is the same
until we get to speed settings of less than -0.5 (where we have an effectively
flat profile anyway). So up to the (speed-dependent) threshold, the mouse speed
is always the same.

Let's look at the comparison of libinput's speed setting to the accel
setting in the simple profile:

I'm not posting the threshold comparison, as
Part
1 shows it does not effect the maximum acceleration factor anyway.

Analysis

So, where does this leave us? I honestly don't know. The curves are different
but the only paper I could find on comparing acceleration curves is Casiez
and Roussel' 2011 UIST paper. It provides a comparison of the X server
acceleration with the Windows and OS X acceleration curves [1]. It shows
quite a difference between the three systems but the authors note that no
specific acceleration curve is definitely superior. However, the most
interesting bit here is that both the Windows and the OS X curve seem to
be constant acceleration (with very minor changes) rather than changing the
curve shape.

Either way, there is one possible solution for libinput to
implement: to change the base plateau with the speed. Otherwise libinput's
acceleration curve is well defined for the
configurable range. And a maximum acceleration factor of 3.5 is plenty
for a properly configured mouse (generally anything above 3 is tricky
to control). AFAICT, the main issues with pointer
acceleration come from mice that either don't have MOUSE_DPI set or
trackpoints which are, unfortunately, a completely different problem.

I'll probably also give the windows/OS X approaches a try (i.e. same curve,
different constant deceleration) and see how that goes. If it works well, that
may be a a solution because it's easier to scale into a large range. Otherwise,
*shrug*, someone will have to come with a better solution.

[1] I've never been able to reproduce the same gain (== factor) but at
least the shape and x axis seems to match.

In Part
1 I showed the X server acceleration code as used by the evdev driver
(which leaves all acceleration up to the server). In this part, I'll show
the acceleration code as used by the synaptics touchpad driver. This driver
installs a device-specific acceleration profile but beyond that the
acceleration is... difficult.
The profile itself is not necessarily indicative of the real movement, the
coordinates are scaled between device-relative, device-absolute,
screen-relative, etc. so often that it's hard to keep track of what the real
delta is. So let's look at the profile only.

Diagram generation

Diagrams were generated by gnuplot, parsing .dat files generated by the
ptrveloc tool in the git repo. Helper scripts to regenerate all data
are in the repo too. Default values unless otherwise specified:

MinSpeed: 0.4

MaxSpeed: 0.7

AccelFactor: 0.04

dpi: 1000 (used for converting units to mm)

All diagrams are limited to 100 mm/s and a factor of 5 so they are directly comparable.
From earlier testing I found movements above over 300 mm/s are rare, once you hit 500 mm/s the
acceleration doesn't really matter that much anymore, you're going to hit the screen
edge anyway.

The choice of 1000 dpi is a difficult one. It makes the diagrams directly comparable to those in
Part 1
but touchpads have a great variety in their resolution. For example, an ALPS
DualPoint touchpad may have resolutions of 25-32 units/mm. A Lenovo T440s has a resolution
of 42 units/mm over PS/2 but 20 units/mm over the newer SMBus/RMI4 protocol.
This is the same touchpad. Overall it doesn't actually matter that much though, see below.

The acceleration profile

This driver has a custom acceleration profile, configured by the
MinSpeed, MaxSpeed and AccelFactor options. The former
two put a cap on the factor but MinSpeed also adjusts (overwrites)
ConstantDeceleration. The AccelFactor defaults to a device-specific
size based on the device diagonal.

Let's look at the defaults of 0.4/0.7 for min/max and 0.04 (default on my
touchpad) for the accel factor:

The simple profile from part 1 is shown in this graph for comparison.
The synaptics profile is printed as two curves,
one for the profile output value and one for the real value used on the delta.
Unlike the simple profile you cannot configure ConstantDeceleration separately,
it depends on MinSpeed. Thus the real acceleration factor is always less than
1, so the synaptics driver doesn't accelerate as such, it controls how much the
deltas are decelerated.

The actual acceleration curve is just a plain old linear interpolation between
the min and max acceleration values. If you look at the curves closer you'll
find that there is no acceleration up to 20mm/s and flat acceleration from
25mm/s onwards. Only in this small speed range does the driver adjust its
acceleration based on input speed. Whether this is in intentional or just
happened, I don't know.

The accel factor depends on the touchpad x/y axis. On my T440s using PS/2, the
factor defaults to 0.04. If I get it to use SMBus/RMI4 instead of PS/2, that
same device has an accel factor of 0.09. An ALPS touchpad may have a factor of
0.13, based on the min/max values for the x/y axes. These devices
all have different resolutions though, so here are the comparison graphs
taking the axis range and the resolution into account:

The diagonal affects the accel factor, so these three touchpads (two curves
are the same physical touchpad, just using a different bus) get slightly
different acceleration curves. They're more similar than I expected though
and for the rest of this post we can get away we just looking at the 0.04
default value from my touchpad.

Note that due to how synaptics is handled in the server, this isn't
the whole story, there is more coordinate scaling etc. happening after the
acceleration code. The synaptics acceleration profile also does not
acccommodate for uneven x/y resolutions, this is handled in the server
afterwards. On touchpads with uneven resolutions the velocity thus depends on
the vector, moving along the x axis provides differently sized deltas than
moving along the y axis. However, anything applied later isn't speed dependent
but merely a constant scale, so these curves are still a good representation
of what happens.

The effect of configurations

What does the acceleration factor do? It changes when acceleration kicks in
and how steep the acceleration is.

MinSpeed lifts the baseline (i.e. the minimum acceleration factor), somewhat
expected from a parameter named this way. But it looks again like we have a bug
here. When MinSpeed and MaxSpeed are close together, our acceleration actually
decreases once we're past the threshold. So counterintuitively, a higher
MinSpeed can result in a slower cursor once you move faster.

The same bug is present, if the MaxSpeed is smaller or close to MinSpeed,
our acceleration actually goes down. A quick check of the sources didn't
indicate anything enforcing MinSpeed < MaxSpeed either. But otherwise
MaxSpeed lifts the maximum acceleration factor.

These graphs look at the options in separation, in reality users would
likely configure both MinSpeed and MaxSpeed at the same time. Since both
have an immediate effect on pointer movement, trial and error configuration
is simple and straightforward. Below is a graph of all three adjusted semi-randomly:

No suprises in there, the baseline (and thus slowest speed) changes, the
maximum acceleration changes and how long it takes to get there changes. The curves
vary quite a bit though, so without knowing the configuration options,
it's impossible to predict how a specific touchpad behaves.

Epilogue

The graphs above show the effect of configuration options in the synaptics driver.
I purposely didn't put any specific analysis in and/or compare it to libinput.
That comes in a future post.

Over the last few days, I once again tried to tackle pointer
acceleration. After all, I still get plenty of complaints about how terrible
libinput is and how the world was so much better without it. So I once more
tried to understand the X server's pointer
acceleration code. Note: the evdev driver doesn't do any acceleration, it's
all handled in the server. Synaptics will come in part two, so this here focuses mostly on pointer
acceleration for mice/trackpoints.

After a few failed attempts of live analysis [1], I finally succeeded extracting the pointer
acceleration code into something that could be visualised. That helped me a
great deal in going back and understanding the various bits and how they fit
together.

The approach was: copy the ptrveloc.(c|h) files into a new project, set up
a meson.build file, #define all the bits that are assumed to be there and
voila, here's your library. Now we can build basic analysis tools provided
we initialise all the structs the pointer accel code needs correctly. I think
I succeeded. The git repo is here if anyone
wants to check the data. All scripts to generate the data files are in the
repository.

A note on language: the terms "speed" and "velocity" are subtly different but
for this post the difference doesn't matter. The code uses "velocity" but "speed" is
more natural to talk about, so just assume equivalence.

The X server acceleration code

There are 15 configuration options for pointer acceleration
(ConstantDeceleration, AdaptiveDeceleration, AccelerationProfile,
ExpectedRate, VelocityTrackerCount, Softening, VelocityScale, VelocityReset,
VelocityInitialRange, VelocityRelDiff, VelocityAbsDiff,
AccelerationProfileAveraging, AccelerationNumerator,
AccelerationDenominator, AccelerationThreshold). Basically, every number
is exposed as configurable knob. The acceleration code is a product of a
time when we were handing out configuration options like
participation medals at a children's footy tournament. Assume that for the
rest of this blog post, every behavioural description ends with "unless
specific configuration combinations apply". In reality, I think only four
options are commonly used: AccelerationNumerator, AccelerationDenominator,
AccelerationThreshold, and ConstantDeceleration. These four have immediate effects on
the pointer movement and thus it's easy to do trial-and-error configuration.

The server has different acceleration profiles (called the 'pointer transfer
function' in the literature). Each profile is a function that converts
speed into a factor. That factor is then combined with other things like
constant deceleration, but eventually our output delta forms as:

deltaout(x, y) = deltain(x, y) * factor * deceleration

The output delta is passed back to the server and the pointer saunters
over by few pixels, happily bumping into any screen edge on the way.

The input for the acceleration profile is a speed in mickeys,
a threshold (in mickeys) and a max accel factor (unitless). Mickeys are a bit
tricky. This means the acceleration is device-specific, the
deltas for a mouse at 1000 dpi are 20% larger than the deltas for a mouse at
800 dpi (assuming same physical distance and speed). The "Resolution" option in evdev
can work around this, but by default this means that the acceleration factor
is (on average) higher for high-resolution mice for the same physical
movement. It also means that that xorg.conf snippet you found on
stackoverflow probably does not do the same on your device.

The second problem with mickeys is that they require a frequency to map to a
physical speed. If a device sends events every N ms, delta/N gives us a
speed in units/ms. But we need mickeys for the profiles. Devices
generally have a fixed reporting rate and the speed of each mickey is the same as
(units/ms * reporting rate). This rate defaults to 10 in the server
(the VelocityScaling default value) and thus matches a device
reporting at 100Hz (a discussion of this comes later). All graphs below were
generated with this default value.

Back to the profile function and how it works: The threshold
(usually) defines the mimimum speed at which acceleration kicks in. The
max accel factor (usually) limits the acceleration. So the simplest
algorithm is

In reality, things are somewhere between this simple and "whoops, what have we done".

Diagram generation

Diagrams were generated by gnuplot, parsing .dat files generated by the
ptrveloc tool in the git repo. Helper scripts to regenerate all data
are in the repo too. Default values unless otherwise specified:

threshold: 4

accel: 2

dpi: 1000 (used for converting units to mm)

constant deceleration: 1

profile: classic

All diagrams are limited to 100 mm/s and a factor of 5 so they are directly comparable.
From earlier testing I found movements above over 300 mm/s are rare, once you hit 500 mm/s the
acceleration doesn't really matter that much anymore, you're going to hit the screen
edge anyway.

Acceleration profiles

The server provides a number of profiles, but I have seen very little evidence
that people use anything but the default "Classic" profile. Synaptics installs a
device-specific profile. Below is a comparison of the profiles just so you
get a rough idea what each profile does. For this post, I'll focus on the default
Classic only.

First thing to point out here that if you want to have your pointer travel to
Mars, the linear profile is what you should choose. This profile is unusable
without further configuration to bring the incline to a more sensible level.
Only the simple and limited profiles have a maximum factor, all others
increase acceleration indefinitely. The faster you go, the more it
accelerates the movement. I find them completely unusable at anything but
low speeds.

The classic profile transparently maps to the simple profile, so the curves are identical.

Anyway, as said above, profile changes are rare. The one we care about is
the default profile: the classic profile which transparently maps to the
simple profile (SimpleSmoothProfile() in the source).

The effect of config options

First thing that sticks out: one of these is not like the others. The
classic profile changes to the polynomial profile at thresholds less than
1.0. *shrug* I think there's some historical reason, I didn't chase it up.

Otherwise, the threshold not only defines when acceleration starts
kicking in but it also affects steepness of the curve. So higher threshold
also means acceleration kicks in slower as the speed increases. It has no
effect on the low-speed deceleration.

What happens when we change the max accel factor? This factor is actually
set via the AccelerationNumerator and AccelerationDenominator options
(because floats used to be more expensive than buying a house). At
runtime, the Xlib function of your choice is XChangePointerControl().
That's what all the traditional config tools use (xset, your desktop
environment pre-libinput, etc.).

First thing that sticks out: one is not like the others. When max
acceleration is 0, the factor is always zero for speeds exceeding the
threshold. No user impact though, the server discards factors of 0.0 and
leaves the input delta as-is.

Otherwise it's relatively unexciting, it changes the maximum acceleration
without changing the incline of the function. And it has no effect on
deceleration. Because the curves aren't linear ones, they don't overlap 100%
but meh, whatever. The higher values are cut off in this view, but they just look like a larger version of the visible 2 and 4 curves.

Next config option: ConstantDeceleration. This one is handled outside of the
profile but at the code is easy-enough to follow, it's a basic multiplier
applied together with the factor. (I cheated and just did this in gnuplot
directly)

Easy to see what happens with the curve here, it simply stretches vertically
without changing the properties of the curve itself. If the deceleration is
greater than 1, we get constant acceleration instead.

All this means with the default profile, we have 3 ways of adjusting it.
What we can't directly change is the incline, i.e. the actual process of
acceleration remains the same.

Velocity calculation

As mentioned above, the profile applies to a velocity so obviously we need
to calculate that first. This is done by storing each delta and looking at
their direction and individual velocity. As long as the direction remains
roughly the same and the velocity between deltas doesn't change too much,
the velocity is averaged across multiple deltas - up to 16 in the default config.
Of course you can change whether this averaging applies, the max time
deltas or velocity deltas, etc. I'm honestly not sure anyone ever used any
of these options intentionally or with any real success.

Velocity scaling was explained above (units/ms * reporting rate). The default
value for the reporting rate is 10, equivalent to 100Hz.
Of the 155 frequencies currently defined in 70-mouse.hwdb, only one
is 100 Hz. The most common one here is 125Hz, followed by 1000Hz followed by
166Hz and 142Hz. Now, the vast majority of devices don't have an entry in
the hwdb file, so this data does not represent a significant sample set. But
for modern mice, the default velocity scale of 10 is probably off between 25%
and a factor 10. While this doesn't mean much for the local example
(users generally just move the numbers around until they're happy enough) it
means that the actual values are largely meaningless for anyone but those
with the same hardware.

Of note: the synaptics driver automatically sets VelocityScale to 80Hz. This
is correct for the vast majority of touchpads.

Epilogue

The graphs above show the X server's pointer acceleration for mice, trackballs
and other devices and the effects of the configuration toggles. I purposely did
not put any specific analysis in and/or comparison to libinput. That will come in a
future post.

[1] I still have a branch somewhere where the server prints yaml to the log
file which can then be extracted by shell scripts, passed on to python for
processing and ++++ out of cheese error. redo from start ++++

Monday, April 9, 2018

To reduce the number of bugs filed against libinput consider this a PSA: as of GNOME 3.28, the default click method on touchpads is the 'clickfinger' method (see the libinput documentation, it even has pictures). In short, rather than having a separate left/right button area on the bottom edge of the touchpad, right or middle clicks are now triggered by clicking with 2 or 3 fingers on the touchpad. This is the method macOS has been using for a decade or so.

Prior to 3.28, GNOME used the libinput defaults which vary depending on the hardware (e.g. mac touchpads default to clickfinger, most other touchpads usually button areas). So if you notice that the right button area disappeared after the 3.28 update, either start using clickfinger or reset using the gnome-tweak-tool. There are gsettings commands that achieve the same thing if gnome-tweak-tool is not an option:

Tuesday, January 30, 2018

For the last few weeks, Benjamin Tissoires and I have been working on a new project: Tuhi [1], a daemon to connect to and download data from Wacom SmartPad devices like the Bamboo Spark, Bamboo Slate and, eventually, the Bamboo Folio and the Intuos Pro Paper devices. These devices are not traditional graphics tablets plugged into a computer but rather smart notepads where the user's offline drawing is saved as stroke data in vector format and later synchronised with the host computer over Bluetooth. There it can be converted to SVG, integrated into the applications, etc. Wacom's application for this is Inkspace.

There is no official Linux support for these devices. Benjamin and I started looking at the protocol dumps last year and, luckily, they're not completely indecipherable and reverse-engineering them was relatively straightforward. Now it is a few weeks later and we have something that is usable (if a bit rough) and provides the foundation for supporting these devices properly on the Linux desktop. The repository is available on github at
https://github.com/tuhiproject/tuhi/.

The main core is a DBus session daemon written in Python. That daemon connects to the devices and exposes them over a custom DBus API. That API is relatively simple, it supports the methods to search for devices, pair devices, listen for data from devices and finally to fetch the data. It has some basic extras built in like temporary storage of the drawing data so they survive daemon restarts. But otherwise it's a three-way mapper from the Bluez device, the serial controller we talk to on the device and the Tuhi DBus API presented to the clients. One such client is the little commandline tool that comes with tuhi: tuhi-kete [2]. Here's a short example:

I won't go into the details because most should be obvious and this is purely a debugging client, not a client we expect real users to use. Plus, everything is still changing quite quickly at this point.

The next step is to get a proper GUI application working. As usual with any GUI-related matter, we'd really appreciate some help :)

The project is young and relying on reverse-engineered protocols means there are still a few rough edges. Right now, the Bamboo Spark and Slate are supported because we have access to those. The Folio should work, it looks like it's a re-packaged Slate. Intuos Pro Paper support is still pending, we don't have access to a device at this point. If you're interested in testing or helping out, come on over to the github site and get started!

Monday, January 8, 2018

Edit 2018-02-26: renamed from libevdev-python to python-libevdev. That seems to be a more generic name and easier to package.

Last year, just before the holidays Benjamin Tissoires and I worked on a 'new' project - python-libevdev. This is, unsurprisingly, a Python wrapper to libevdev. It's not exactly new since we took the git tree from 2016 when I was working on it the first time round but this time we whipped it into a better shape. Now it's at the point where I think it has the API it should have, pythonic and very easy to use but still with libevdev as the actual workhorse in the background. It's available via pip3 and should be packaged for your favourite distributions soonish.

Who is this for? Basically anyone who needs to work with the evdev protocol. While C is still a thing, there are many use-cases where Python is a much more sensible choice. The python-libevdev documentation on ReadTheDocs provides a few examples which I'll copy here, just so you get a quick overview. The first example shows how to open a device and then continuously loop through all events, searching for button events:

import libevdev
fd = open('/dev/input/event0', 'rb')
d = libevdev.Device(fd)
if not d.has(libevdev.EV_KEY.BTN_LEFT):
print('This does not look like a mouse device')
sys.exit(0)
# Loop indefinitely while pulling the currently available events off
# the file descriptor
while True:
for e in d.events():
if not e.matches(libevdev.EV_KEY):
continue
if e.matches(libevdev.EV_KEY.BTN_LEFT):
print('Left button event')
elif e.matches(libevdev.EV_KEY.BTN_RIGHT):
print('Right button event')

The second example shows how to create a virtual uinput device and send events through that device:

The latter is particularly helpful if you have a script that needs to analyse event sequences and look for protocol bugs (or hw/fw issues).

More explanations and details are available in the python-libevdev documentation. That doc also answers the question why python-libevdev exists when there's already a python-evdev package. The code is up on github.