X.org mouse acceleration proposal Author: Simon Thum (simon [dot] thum [at] gmx de) Date: Jul 2008 Intent - improve heavy-load behaviour (no overshooting pointer) - enable accuracy and fluid motion with every pointing device - provide a better 'feel' for X pointing devices Postulate When the pointer gets accelerated with respect to device motion, it becomes important for the user to be able to predict that acceleration. Without this, the user is unable to intuitively use his device. Thus, it is presumed that the most critical part is doing a sophisticated estimate about the velocity of the device in a users hand, as this is the primary information the users brain has to build its knowledge about the applied translation from device to screen. Because this is an intuitive process, easing it just 'feels better'. Current problems adressed 1) For polynomial acceleration: On slow movements, current code infers 3 'velocities': 1, 1.41, and 2. As 1 remains unaccelerated, we're left with just 2 acceleration levels. It is hard to foresee what exactly will happen. Worse, when using the simple accelerated/unaccelerated scheme, acceleration either comes to effect or it doesn't, providing no predictability around the threshold. 2) If a system is under load, a device may accumulate its movement delta while the system does not query the device, causing irrational high cursor movement. This is particulary annoying when caused by focus changes during a mouse stroke. 3) Some people own very responsive devices, creating a need to reduce speed on precise tasks or even in general. 4) With the simple acceleration scheme, acceleration is more sensitive on diagonal movements than on axis-aligned ones. These problems result in a reduced ability for our intuition to predict a matching hand movement for desired screen movement, causing more correctional moves than neccessary. Put simply, the mouse 'feels bad'. Benefits Mostly, the polynomial acceleration becomes more usable. It can be used with higher acceleration coefficients (acc > 2), still providing enough control. But also the classic acceleration should become less jumpy since it now graduates (rather) soft towards accelerated motion. Users with too precise devices can slow them without loosing precision, independent of hardware driver support. Also, an acceleration function may decelerate on slow movements, giving (sub)pixel precision without sacrificing on pointer speed. The code is more robust towards different devices: One could imagine a mouse reporting very often, but only 1 dot per event. Old code would not accelerate such a device at all. While this is a theoretical case, there is robustness against jitter in device event frequency (as could be caused by system load, for example). By introducing a coefficient in xorg.conf (VelocityScale or ExpectedRate) you can make two attached devices feel similar or intentionally dissimilar. A driver which wants to roll its own acceleration now can do so without risk of distributing stale data along the event chain. See 'Driver side' below. Users disliking all this can switch it off. Useage If your X bears the patch, all you need to do is use it. Type > xset m 18/10 0 to set a moderate polynomial acceleration. This works without the patch too, but the previous method is not very nice. More advanced feartures are available via config file (see below). Method 1) A better approximation of velocity is done. Given available data, we calc dots per millisecond. A correction is applied to account for slow diagonal moves which are reported as alternating horizontal/vertical mickeys. These would otherwise be guessed 41% too fast. Dots per milisecond is quite intractable in integers. X controls are integer in part, and changing that would break too much. Thus, we multiply by a configurable factor to arrive at values where the usual X controls can be applied. This velocity is then fed into a chain of filters. Well, by default, it is one filter, at max there are 8. This means the velocity is tracked by potentially several filters, each with different response. After that, the most integrating filter response in range with the current velocity is selected. This ensures good responsivity and a likely precise velocity estimate. However, even the one filter used by default is enough for most people/devices. Typically during begin and end of a mouse stroke, filtering is not approriate. Therefore, a coupling is employed to override velocity if filter responses seem inapproriate. Basically, you can either rely on coupling to provide good responsivity or have a filter suited to every phase of a stroke and loosen coupling. After some short inactivity time (300 ms by default), the background data is discarded (called non-visible state (reset) in the patch). This is to ensure no two distinct strokes affect each other. 2) Acceleration functions are made continuous. This avoids sudden jumps in the amount of acceleration, enhancing intuitivity furter. 3) Mickeys are slightly flattened This aims to improve evolving-speed movements such as painting with a mouse typically requires. It happens just below device precision and only if acceleration is actually performed. It can be turned off. 4) For too responsive devices, there are two possibilities (together if desired): 1) a constant deceleration can be applied 2) acceleration function(s) can be allowed to decelerate on slow movements (adaptive deceleration) Adaptive deceleration is a good tool allow precise pointing while maintaining pointer speed. Configuration The defaults should suffice if you had no problems before, and feel quite similar. The settings discussed here are the coarse and the very subtle settings, not the usual xset or GUI panel controls you might know. Because this is a drop-in enhancement, the usual threshold and acceleration controls work as expected and are not planned to be replaced. However, different profiles might react different to them. The settings described below are device-specific and need to be set in the approriate "InputDevice" section in xorg.conf. Usually it looks like: Section "InputDevice" Identifier "Mouse0" Driver "mouse" [...] EndSection For example to enable the adaptive deceleration feature, put in a line reading Option "AdaptiveDeceleration" "2" or similar. A few tips If your mouse moves far too fast, ConstantDeceleration is your friend. Set to 2 or higher to divide speed accordingly. This will not discard precision (at least only on nv-reset, see Method or below). If you like the speed but need some more control at pixel-level, you should set AdaptiveDeceleration to 2 or more. This allows to decelerate slow movements down to the given factor. You might want to keep nv-resets away by setting VelocityReset to e.g. 500 ms, and maybe tweak VelocityScale to tune results. If you are picky about a smooth kick-in of acceleration, for example to ease doing art, I suggest using profile 1, enabling adaptive deceleration and tweaking VelocityScale (in that order). Maybe loosening velocity coupling also helps it. Options AdaptiveDeceleration [real] Allows the acceleration function to actually decelerate the pointer by the given factor. Default is 1. ConstantDeceleration [real] Constantly decelerates the mouse by given factor. Using ConstantDeceleration should be preferred over corresponding device driver options (if any) since these retain data which could worsen the prediction of device velocity. Implemetation note: This factor is applied onto the device velocity estimate, so the actual acceleration relates more to screen motion. VelocityScale [real] or ExpectedRate [real (Hz)] In short, this controls sensitivity of acceleration. This setting is designed to be device-dependent, i.e. you set it once to match your device, then modify behaviour using the well-known X controls. It is important to note there is no 'right' factor, just one that bears the nice property of matching to X controls just like the old code did. Rationale: Device deltas are being divided by delta milliseconds before being weighted, so they are about 10 times too small compared to a device reporting every 10 ms. Because the reporting rate is usually unknown in advance, this is the only way to scale up to 'normal' values as well as identify slow movement. Default is 10, or 10x, which is suitable for devices reporting around 100hz. Currently, no attempt is made to guess the rate. AccelerationProfile [int] Select the acceleration profile. Default is 0, except if the driver says otherwise (none currently does). 0 classic (similar to old behaviour, but more useable) 1 device-dependent (available if the hw driver installs it) 2 polynomial (equivalent to classic with threshold = 0) 3 smooth linear (scales linear, but with a smooth start) 4 simple (equivalent to classic accelerated/unaccelerated) 5 power (accelerates by a power function. for advanced users) 6 linear (just linear to velocity) FilterHalflife [real] The halflife of the weighting applied in order to approximate velocity. Higher values exhibit more integrating behaviour, introducing some lag but also may feel smoother. Less is more responsive, but less smooth. Higher values may need to be accompanied by a looser coupling, or it won't come to effect. Default: 20 ms. FilterChainProgression [real] FilterChainLength [integer] If you want to try a filter chain, set FilterHalflife to some small value. For example, for a device reporting every 10 ms, 5 to 10. The first filter will use this halflife, which should be very responsive. The next ChainLenght -1 filters will be progressively more integrating (thus less responsive). The progression should be high enough so the last filter can average a few motion reports. You may want to loosen coupling in such a setup. Example filter halflifes for length 4, halfllife 5, progression 2: 5 10 20 40 Of course, more filters are computationially more intensive. VelocityReset [integer] Specifies after how many milliseconds of inactivity non-visible state (i.e. subpixel position) is discarded. This affects three issues: 1) Two mouse strokes do not have any effect on each other if they are [VelocityReset] miliseconds from each other. This would be neglible though. 2) Velocity estimate remains correct within this time if the pointer/X is stuck for a short moment, not querying the moving device. 3) slow movements are guessed correctly if all device movement events are inside this time from each other. An increment might be neccessary to fully take advantage of adaptive deceleration. Default 300 ms. VelocityCoupling [real] Specifies coupling, a feature ensuring responsivity by determining if the filters velocity estimate is a valid match for the current velocity. The weighted estimate is deemed valid if it differs from current either below 1.0 (hardcoded) or below this factor. Should it be deemed invalid, velocity will be overridden by the current velocity and any filter data is discarded. Higher values mean it is more likely that filtered velocity is deemed valid, so a lower value tightens coupling, a higher value loosens coupling. Default is 0.25, or 25%. Softening [boolean] Tweaks motion deltas from device before applying acceleration to smooth out rather constant moves. Tweaking is always below device precision to make sure it doesn't get in the way. Also, when ConstantDeceleration is used, Softening is not enabled by default because this provides better (i.e. real) subpixel information. AccelerationScheme [string] Select Scheme. All other options only apply to scheme 1 (default). none disable acceleration/deceleration predictable the Scheme discussed here (default) old previous scheme (i.e. exactly as in X server 1.5) Acceleration functions/profiles/schemes (for the inclined programmer) Acceleration functions translate device velocity into an acceleration to be imposed on the pointer. Xorg currently offers two functions: Simple and polynomial. They are selected somewhat strange through the threshold control: threshold = 0 means polynomial, simple otherwise. The simple acceleration function is now continuous, and the polynomial maintains f(1) = 1. They are designed to mimic previous behaviour, so they are wrapped in the classic profile. Just copying old functions would not provide much benefit: The patch would make the point when acceleration is performed be more predictable, but not cause the pointer to cease jumping around that point. If you like to play with the functions, a few nice properties: (1) f(1) ~ 1 a fixed point, to enable exchanging functions (2) continuous very nice-to-have since we would otherwise throw away our data (probably causing jumps) (3) continuous over derivative(s) or Cn nice to have for smoothness. (4) f'(min_acceleration) = 0 Ensures a soft kick-in of acceleration (5) f( < 1) < 1 enables adaptive deceleration - although it is possible to hold all of the properies, included functions only hold (1), (2), (5), and some also (3). - acceleration functions are not meant to enforce constant or adaptive deceleration. This is done in a separate step. - notwithstanding the former, functions might adapt to min_accceleration to uphold (4). - In X an acceleration coefficient of 1 is unaccelerated, not 0. It can be specified a a rational but we convert it to float. - usual control values should not make your fn go havoc. See PowerProfile() for a measure one can take. If you want to do freaky new functions, you best put them in an own profile. Add your function to SetAccelerationProfile(), along with init/uninit code, and you're done. While tempting, runtime-defined functions are currently not possible. API extension are on the way. A scheme is on top of the hierarchy, and if you think this is all bullshit and your algorithm rocks you should do an own scheme. Currently, 'plain old X' and the scheme discussed here are implemented. Driver side In general, a driver does not need to act in any way. The acceleration is initialized during InitValuatorClassDeviceStruct, user settings on acceleration are loaded when a driver calls xf86InitValuatorDefaults. These are already called by about every driver. In general, driver-specific interaction should be before xf86IVD, so user settings take precedence. Most interaction requires a DeviceVelocityPtr. Use GetDevicePredictableAccelData(DeviceIntPtr) to obtain it (may return null). If a driver knows the anticipated reporting rate of the device in advance, it might choose to override the default to somewhat improve velocity estimates: your_velocityPtr.corr_mul = 1000/rate; Also, if your device has very high precision, you can postpone downscaling: your_velocityPtr.const_acceleration = 0.5f; /* 1/2 */ This makes the full device precision available to guess velocity, and has potentially more benefits (in some bright future). Plus, you don't have to do it :) A hardware driver may use its knowledge about the device to create a special acceleration profile. This can be installed using SetDeviceSpecificAccelerationProfile() In order to make it the default for the device, simply call SetAccelerationProfile(velocityPtr, AccelProfileDeviceSpecific); The user may always select it using profile 1. If you ultimately want no server-side acceleration to be performed, call InitPointerAccelerationScheme(dev, PtrAccelNoOp). This disables constant deceleration too. Final notes Problems / Todo Runtime modification of acceleration parameters should be possible. However in this version the functionality has been stripped. Any work should probably be based on input properties. The Patch is only for xorg; kdrive has not been touched. Since it is in dix, this is not a big problem. Hotplug devices haven't been tested but should work. Interaction with synaptics / about any smart driver I noticed two important things to consider when using the synaptics driver (or any other driver doing substantially more than decoding mickeys): 1) It seems synaptics driver implements its own acceleration, which can be switched off. Two ways of acceleration certainly don't do good. This can be accomplished by setting 2 options, 'MaxAcc' and 'MinAcc' to the same. 2) I chose MaxAcc = MinAcc = 1, which seemingly made the native touchpad resolution available to X, which in turn was far too responsive. I had to apply a ConstantDeceleration of 8 to work with it. This also makes the full device precision available to guess velocity. Also, I found increasing WeightingDecay on the touchpad to feel more comfortable. This probably holds for every driver passing on high precision values. As said, you have the choice on who scales down, and the driver could well be the better choice because it knows its stuff. But if it isn't better, choosing X to scale down will result in better velocity approximation, which may be advantageous. Reference https://bugs.freedesktop.org/show_bug.cgi?id=8583 http://lists.freedesktop.org/archives/xorg/2005-September/010211.html https://bugs.freedesktop.org/show_bug.cgi?id=138 https://bugs.freedesktop.org/show_bug.cgi?id=2927