diff --git a/COPYING b/COPYING index 8bbb3c38..efc1a949 100644 --- a/COPYING +++ b/COPYING @@ -1,3 +1,4 @@ +Copyright © 2006-2009 Simon Thum Copyright © 2008-2012 Kristian Høgsberg Copyright © 2010-2012 Intel Corporation Copyright © 2010-2011 Benjamin Franzke diff --git a/configure.ac b/configure.ac index 141f97ac..d321e87b 100644 --- a/configure.ac +++ b/configure.ac @@ -1,7 +1,7 @@ AC_PREREQ([2.64]) m4_define([libinput_major_version], [0]) -m4_define([libinput_minor_version], [13]) +m4_define([libinput_minor_version], [15]) m4_define([libinput_micro_version], [0]) m4_define([libinput_version], [libinput_major_version.libinput_minor_version.libinput_micro_version]) @@ -31,7 +31,7 @@ AM_INIT_AUTOMAKE([1.11 foreign no-dist-gzip dist-xz]) # b) If interfaces have been changed or added, but binary compatibility has # been preserved, change to C+1:0:A+1 # c) If the interface is the same as the previous version, change to C:R+1:A -LIBINPUT_LT_VERSION=10:1:0 +LIBINPUT_LT_VERSION=12:0:2 AC_SUBST(LIBINPUT_LT_VERSION) AM_SILENT_RULES([yes]) @@ -60,6 +60,20 @@ PKG_PROG_PKG_CONFIG() PKG_CHECK_MODULES(MTDEV, [mtdev >= 1.1.0]) PKG_CHECK_MODULES(LIBUDEV, [libudev]) PKG_CHECK_MODULES(LIBEVDEV, [libevdev >= 0.4]) +PKG_CHECK_MODULES(LIBUNWIND, + [libunwind], + [HAVE_LIBUNWIND=yes], + [HAVE_LIBUNWIND=no]) +if test "x$HAVE_LIBUNWIND" = "xyes"; then + AC_DEFINE(HAVE_LIBUNWIND, 1, [Have libunwind support]) +fi +AM_CONDITIONAL(HAVE_LIBUNWIND, [test "x$HAVE_LIBUNWIND" = xyes]) +AC_PATH_PROG(ADDR2LINE, [addr2line]) +if test "x$ADDR2LINE" != "x"; then + AC_DEFINE_UNQUOTED(HAVE_ADDR2LINE, 1, [addr2line found]) + AC_DEFINE_UNQUOTED(ADDR2LINE, ["$ADDR2LINE"], [Path to addr2line]) +fi + AC_CHECK_LIB([m], [atan2]) AC_CHECK_LIB([rt], [clock_gettime]) @@ -201,5 +215,6 @@ AC_MSG_RESULT([ Build documentation ${build_documentation} Build tests ${build_tests} Tests use valgrind ${VALGRIND} + Tests use libunwind ${HAVE_LIBUNWIND} Build GUI event tool ${build_eventgui} ]) diff --git a/doc/Makefile.am b/doc/Makefile.am index 271960e3..48e68b8a 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -1,4 +1,7 @@ -EXTRA_DIST = touchpad-tap-state-machine.svg touchpad-softbutton-state-machine.svg +EXTRA_DIST = \ + middle-button-emulation.svg \ + touchpad-tap-state-machine.svg \ + touchpad-softbutton-state-machine.svg if BUILD_DOCS @@ -16,7 +19,8 @@ header_files = \ $(srcdir)/seats.dox \ $(srcdir)/t440-support.dox \ $(srcdir)/tablet-support.dox \ - $(srcdir)/tapping.dox + $(srcdir)/tapping.dox \ + $(srcdir)/test-suite.dox diagram_files = \ $(srcdir)/dot/seats-sketch.gv \ diff --git a/doc/device-configuration-via-udev.dox b/doc/device-configuration-via-udev.dox index fc1c0af8..e38b93f6 100644 --- a/doc/device-configuration-via-udev.dox +++ b/doc/device-configuration-via-udev.dox @@ -44,7 +44,10 @@ udev_device_type. ID_INPUT_TOUCHSCREEN, ID_INPUT_TABLET, ID_INPUT_JOYSTICK, ID_INPUT_ACCELEROMETER
If any of the above is set, libinput initializes the device as the given -type, see @ref udev_device_type.
+type, see @ref udev_device_type. Note that for historical reasons more than +one of these may be set at any time, libinput will select only one of these +to determine the device type. To ensure libinput selects the correct device +type, only set one of them.
WL_SEAT
Assigns the logical seat for this device. See libinput_seat_get_logical_name() @@ -57,6 +60,13 @@ See @ref motion_normalization for details.
The angle in degrees for each click on a mouse wheel. See libinput_pointer_get_axis_source() for details.
+
POINTINGSTICK_CONST_ACCEL
+
A constant (linear) acceleration factor to apply to pointingstick deltas +to normalize them. +
LIBINPUT_MODEL_*
+
This prefix is reserved as private API, do not use.. See @ref +model_specific_configuration for details. +
Below is an example udev rule to assign "seat1" to a device from vendor @@ -91,4 +101,16 @@ ACTION=="add|change", KERNEL=="event[0-9]*", ENV{ID_VENDOR_ID}=="012a", \ ENV{ID_MODEL_ID}=="034b", ENV{ID_INPUT_TOUCHPAD}="", ENV{ID_INPUT_TABLET}="1" @endcode +@section model_specific_configuration Model-specific configuration + +libinput reserves the property prefix LIBINPUT_MODEL_ for +model-specific configuration. This prefix is reserved as private API, do +not use. + +The effect of this property may be to enable or disable certain +features on a specific device or set of devices, to change configuration +defaults or any other reason. The effects of setting this property, the +format of the property and the value of the property are subject to change +at any time. + */ diff --git a/doc/middle-button-emulation.svg b/doc/middle-button-emulation.svg new file mode 100644 index 00000000..338af386 --- /dev/null +++ b/doc/middle-button-emulation.svg @@ -0,0 +1,1315 @@ + + + + + + + + + +
+
+ IDLE
+
+
+
+ + [Not supported by viewer] +
+
+ + + + +
+
+ left button
+ press
+
+
+
+ + [Not supported by viewer] +
+
+ + + + +
+
+ right button
+ press
+
+
+
+ + [Not supported by viewer] +
+
+ + + + +
+
+ other button
+ press
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + + + +
+
+ LEFT_DOWN
+
+
+ + [Not supported by viewer] +
+
+ + + + + + +
+
+ RIGHT_DOWN
+
+
+ + [Not supported by viewer] +
+
+ + + + +
+
+ PASSTHROUGH
+
+
+ + [Not supported by viewer] +
+
+ + + + + + +
+
+ left button
+ release
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + +
+
+ right button
+ press
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + +
+
+ timeout
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + +
+
+ BTN_LEFT
+ PRESS
+
+
+
+ + [Not supported by viewer] +
+
+ + + + +
+
+ BTN_LEFT
+ RELEASE
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + +
+
+ BTN_LEFT
+ PRESS
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + +
+
+ MIDDLE
+
+
+ + [Not supported by viewer] +
+
+ + + + +
+
+ BTN_MIDDLE
+ PRESS
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + +
+
+ left button
+ release
+
+
+
+ + [Not supported by viewer] +
+
+ + + + +
+
+ right button
+ release
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + +
+
+ BTN_MIDDLE
+ RELEASE
+
+
+
+ + [Not supported by viewer] +
+
+ + + + +
+
+ BTN_MIDDLE
+ RELEASE
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + +
+
+ LUP_PENDING
+
+
+ + [Not supported by viewer] +
+
+ + + + +
+
+ RUP_PENDING
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + +
+
+ left button
+ release
+
+
+
+ + [Not supported by viewer] +
+
+ + + + +
+
+ right button
+ release
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + +
+
+ IDLE
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + +
+
+ right button
+ press
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + +
+
+ left button
+ press
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + + + +
+
+ other button
+ press
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + +
+
+ left button
+ press
+
+
+
+ + [Not supported by viewer] +
+
+ + + + +
+
+ right button
+ release
+
+
+
+ + [Not supported by viewer] +
+
+ + + + +
+
+ timeout
+
+
+ + [Not supported by viewer] +
+
+ + + + +
+
+ other button
+ press
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + +
+
+ BTN_RIGHT
+ PRESS
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + + + + + +
+
+ BTN_RIGHT
+ PRESS
+
+
+
+ + [Not supported by viewer] +
+
+ + + + +
+
+ BTN_RIGHT
+ RELEASE
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + + + + + + + +
+
+ other button
+ press
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + +
+
+ BTN_MIDDLE
+ RELEASE
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + +
+
+ IGNORE_LR
+
+
+ + [Not supported by viewer] +
+
+ + + + +
+
+ left button
+ release
+
+
+
+ + [Not supported by viewer] +
+
+ + + + +
+
+ right button
+ release
+
+
+
+ + [Not supported by viewer] +
+
+ + + + +
+
+ other button
+ event
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + + + +
+
+ PASSTHROUGH
+
+
+ + [Not supported by viewer] +
+
+ + + + +
+
+ IGNORE_R
+
+
+ + [Not supported by viewer] +
+
+ + + + +
+
+ IGNORE_L
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + +
+
+ left button
+ press
+
+
+
+ + [Not supported by viewer] +
+
+ + + + +
+
+ BTN_LEFT
+ PRESS
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + + + +
+
+ left button
+ release
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + +
+
+ BTN_LEFT
+ RELEASE
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + +
+
+ right button
+ release
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + +
+
+ right button
+ release
+
+
+
+ + [Not supported by viewer] +
+
+ + + + +
+
+ right button
+ press
+
+
+
+ + [Not supported by viewer] +
+
+ + + + +
+
+ BTN_RIGHT
+ PRESS
+
+
+
+ + [Not supported by viewer] +
+
+ + + + +
+
+ BTN_RIGHT
+ RELEASE
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + + + + + + + + + +
+
+ left button
+ release
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + +
+
+ other button
+ press
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + +
+
+ other button
+ press
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + +
+
+ BTN*
+ event
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + +
+
+ BTN*
+ event
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + +
+
+ BTN*
+ event
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + +
+
+ BTN*
+ event
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + +
+
+ BTN*
+ event
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + +
+
+ BTN_LEFT
+ PRESS
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + +
+
+ BTN_RIGHT
+ PRESS
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + +
+
+ any button
+ event
+
+
+
+ + [Not supported by viewer] +
+
+ + + + +
+
+ BTN*
+ event
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + +
+
+ all buttons
+ up?
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + +
+
+ no
+
+
+ + [Not supported by viewer] +
+
+ + + + +
+
+ yes
+
+
+ + [Not supported by viewer] +
+
+ + + + +
+
+ other button
+ event
+
+
+
+ + [Not supported by viewer] +
+
+ + + + +
+
+ BTN*
+ event
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + + + +
+
+ other button
+ event
+
+
+
+ + [Not supported by viewer] +
+
+ + + + +
+
+ BTN*
+ event
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + + + +
+
+ any button
+ event
+
+
+
+ + [Not supported by viewer] +
+
+ + + + +
+
+ BTN*
+ event
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + +
+
+ all buttons
+ up?
+
+
+
+ + [Not supported by viewer] +
+
+ + + + + + + + +
+
+ no
+
+
+ + [Not supported by viewer] +
+
+ + + + +
+
+ IDLE
+
+
+ + [Not supported by viewer] +
+
+ + + + + +
+
+ yes
+
+
+ + [Not supported by viewer] +
+
+
+
diff --git a/doc/palm-detection.dox b/doc/palm-detection.dox index 4e839e62..a03f9c14 100644 --- a/doc/palm-detection.dox +++ b/doc/palm-detection.dox @@ -23,6 +23,10 @@ screen, it is common for a finger to start inside an exclusion zone and move rapidly across the touchpad. libinput detects such movements and avoids palm detection on such touch sequences. +Each exclusion zone is divided into a top part and a bottom part. A touch +starting in the top part of the exclusion zone does not trigger a +tap (see @ref tapping). + In the diagram below, the exclusion zones are painted red. Touch 'A' starts inside the exclusion zone and moves almost vertically. It is considered a palm and ignored for cursor movement, @@ -31,6 +35,11 @@ despite moving out of the exclusion zone. Touch 'B' starts inside the exclusion zone but moves horizontally out of the zone. It is considered a valid touch and controls the cursor. +Touch 'C' occurs in the top part of the exclusion zone. Despite being a +tapping motion, it does not generate an emulated button event. Touch 'D' +likewise occurs within the exclusion zone but in the bottom half. libinput +will generate a button event for this touch. + @image html palm-detection.svg @section trackpoint-disabling Palm detection during trackpoint use @@ -48,5 +57,26 @@ the palm on the touchpad while using the trackstick). If the touchpad is disabled, the @ref t440_support "top software buttons" remain enabled. +@section disable-while-typing Disable-while-typing + +libinput automatically disables the touchpad for a timeout after a key +press, a feature traditionally referred to as "disable while typing" and +previously available through the +[syndaemon(1)](http://linux.die.net/man/1/syndaemon) command. libinput does +not require an external command and the feature is currently enabled for all +touchpads but will be reduced in the future to only apply to touchpads where +finger width or pressure data is unreliable. + +Notable behaviors of libinput's disable-while-typing feature: +- Two different timeouts are used, after a single key press the timeout is + short to ensure responsiveness. After multiple key events, the timeout is + longer to avoid accidental pointer manipulation while typing. +- Some keys do not trigger the timeout, specifically some modifier keys + (Ctrl, Alt, Shift, and Fn). Actions such as Ctrl + click thus stay + responsive. +- Touches started while the touchpad is disabled do not control the cursor, + it is thus possible to rest the palm on the touchpad while typing. +- Physical buttons work even while the touchpad is disabled. This includes + software-emulated buttons. */ diff --git a/doc/scrolling.dox b/doc/scrolling.dox index 3ded909d..b5a01cf8 100644 --- a/doc/scrolling.dox +++ b/doc/scrolling.dox @@ -1,7 +1,20 @@ /** @page scrolling Scrolling -libinput supports three different types of scrolling behavior. +libinput supports three different types of scrolling methods: @ref +twofinger_scrolling, @ref edge_scrolling and @ref button_scrolling. Some devices +support multiple methods, though only one can be enabled at a time. See +libinput_device_config_scroll_set_method() for documentation on how to +switch methods and libinput_device_config_scroll_get_methods() for +documentation on how to query a device for available scroll methods. + +Scroll movements provide vertical and horizontal directions, each +scroll event contains both directions where applicable, see +libinput_event_pointer_get_axis_value(). libinput does not provide separate +toggles to enable or disable horizontal scrolling. Instead, horizontal +scrolling is always enabled. This is intentional, libinput does not have +enough context to know when horizontal scrolling is appropriate for a given +widget. The task of filtering horizontal movements is up to the caller. @section twofinger_scrolling Two-finger scrolling @@ -16,7 +29,6 @@ For scrolling to trigger, a built-in distance threshold has to be met but once engaged any movement will scroll. In other words, to start scrolling a sufficiently large movement is required, once scrolling tiny amounts of movements will translate into tiny scroll movements. - Scrolling in both directions at once is possible by meeting the required distance thresholds to enable each direction separately. @@ -28,23 +40,27 @@ scroll). @image html edge-scrolling.svg "Vertical and horizontal edge scrolling" -Due to the layout of the edges, diagonal scrolling is not possible. +Due to the layout of the edges, diagonal scrolling is not possible. The +behavior of edge scrolling using both edges at the same time is undefined. Edge scrolling conflicts with @ref clickpad_softbuttons and is -not usually available on clickpads. +not usually available on clickpads. See +http://who-t.blogspot.com.au/2015/03/why-libinput-doesnt-support-edge.html +for details. @section button_scrolling On-Button scrolling -Scrolling when a button is held down is available on selected devices. The -motion of a device is converted into scrolling motion. - -For example, Lenovo devices provide a +On-button scrolling converts the motion of a device into scroll events while +a designated button is held down. For example, Lenovo devices provide a pointing stick that emulates scroll events when the trackstick's middle mouse button is held down. @image html button-scrolling.svg "Button scrolling" -Note that libinput's @ref t440_support enables the use of the middle +The button may be changed with +libinput_device_config_scroll_set_button() but must be on the same device as +the motion events. Cross-device scrolling is not supported but +for one exception: libinput's @ref t440_support enables the use of the middle button for button scrolling (even when the touchpad is disabled). */ diff --git a/doc/svg/palm-detection.svg b/doc/svg/palm-detection.svg index 9fb6077d..c3e45f44 100644 --- a/doc/svg/palm-detection.svg +++ b/doc/svg/palm-detection.svg @@ -2,14 +2,66 @@ + id="svg2" + inkscape:version="0.91 r13725" + sodipodi:docname="palm-detection.svg"> + + + + image/svg+xml + + + + + + + + - - - - - - A - B - - + + + + + + A + + B + + C + D + + diff --git a/doc/tapping.dox b/doc/tapping.dox index f603b7d5..7eb81e62 100644 --- a/doc/tapping.dox +++ b/doc/tapping.dox @@ -33,6 +33,10 @@ continue the dragging process, so that multiple touchpad-widths of distance can be covered easily. If two-fingers are supported by the hardware, a second finger can be used to drag while the first is held in-place. +An alternative method to end a drag process is to tap immediately after +lifting the finger. The full sequence is thus: tap, finger down, drag, +finger up, tap. + @section tap_constraints Constraints while tapping A couple of constraints apply to the contact to be converted into a press, the most common ones are: diff --git a/doc/test-suite.dox b/doc/test-suite.dox new file mode 100644 index 00000000..30dd1579 --- /dev/null +++ b/doc/test-suite.dox @@ -0,0 +1,92 @@ +/** +@page test-suite libinput test suite + +The libinput test suite is based on +[Check](http://check.sourceforge.net/doc/check_html/) and runs automatically +during `make check`. Check itself is wrapped into a libinput-specific test +suite called *litest*. Tests are found in `$srcdir/test/`, the test binaries are +prefixed with `test-` and can be run individually. + +@section test-config X.Org config to avoid interference + +uinput devices created by the test suite are usually recognised by X as +input devices. All events sent through these devices will generate X events +and interfere with your desktop. + +Copy the file `$srcdir/test/50-litest.conf` into your `/etc/X11/xorg.conf.d` +and restart X. This will ignore any litest devices and thus not interfere +with your desktop. + +@section test-root Permissions required to run tests + +Most tests require the creation of uinput devices and access to the +resulting `/dev/input/eventX` nodes. Some tests require temporary udev rules. +This usually requires the tests to be run as root. + +@section test-filtering Selective running of tests + +litest's tests are grouped by test groups and devices. A test group is e.g. +"touchpad:tap" and incorporates all tapping-related tests for touchpads. +Each test function is (usually) run with one or more specific devices. +The `--list` commandline argument shows the list of suites and tests. +@code +$ ./test/test-device --list +device:wheel: + wheel only + blackwidow +device:invalid devices: + no device +device:group: + no device + logitech trackball + MS surface cover + mouse_roccat + wheel only + blackwidow +... +@endcode + +In the above example, the "device:wheel" suite is run for the "wheel only" and +the "blackwidow" device. Both devices are automatically instantiated through +uinput by litest. The "no device" entry signals that litest does not +instantiate a uinput device for a specific test (though the test itself may +instantiate one). + +The `--filter-test` argument enables selective running of tests through +basic shell-style function name matching. For example: + +@code +$ ./test/test-touchpad --filter-test="*1fg_tap*" +@endcode + +The `--filter-device` argument enables selective running of tests through +basic shell-style device name matching. The device names matched are the +litest-specific shortnames, see the output of `--list`. For example: + +@code +$ ./test/test-touchpad --filter-device="synaptics*" +@endcode + +The `--filter-group` argument enables selective running of test groups +through basic shell-style test group matching. The test groups matched are +litest-specific test groups, see the output of `--list`. For example: + +@code +$ ./test/test-touchpad --filter-group="touchpad:*hover*" +@endcode + +The `--filter-device` and `--filter-group` arguments can be combined with +`--list` to show which groups and devices will be affected. + +@section test-verbosity Controlling test output + +Each test supports the `--verbose` commandline option to enable debugging +output, see libinput_log_set_priority() for details. The `LITEST_VERBOSE` +environment variable, if set, also enables verbose mode. + +@code +$ ./test/test-device --verbose +$ LITEST_VERBOSE=1 make check +@endcode + +*/ diff --git a/doc/touchpad-tap-state-machine.svg b/doc/touchpad-tap-state-machine.svg index 7aecefc1..39b0b86b 100644 --- a/doc/touchpad-tap-state-machine.svg +++ b/doc/touchpad-tap-state-machine.svg @@ -1,756 +1,1141 @@ - - + + + + + - - - - - - - + + + + + + + - IDLE + + IDLE - + - TOUCH + + TOUCH - + - first - finger down + + first + + finger down - - - + + + - finger up + + finger up - - - + + + - button 1 - press + + button 1 + + press - + - timeout + + timeout - - - + + + - move > - threshold + + move > + + threshold - - - + + + - second - finger down + + second + + finger down - - - + + + - TOUCH_2 + + TOUCH_2 - + - second - finger up + + second + + finger up - - - + + + - button 2 - press + + button 2 + + press - + - move > - threshold + + move > + + threshold - + - timeout + + timeout - - - - - + + + + + - button 1 - release + + button 1 + + release - + - button 2 - release + + button 2 + + release - - - - - + + + + + - TAPPED + + TAPPED - + - timeout + + timeout - - - + + + - first - finger down + + first + + finger down - - - + + + - DRAGGING + + DRAGGING - + - first - finger up + + first + + finger up - + - btn1 - release + + btn1 + + release - - - - - - - + + + + + + + - IDLE + + IDLE - + - third - finger down + + third + + finger down - - - + + + - TOUCH_3 + + TOUCH_3 - - - + + + - button 3 - press + + button 3 + + press - + - button 3 - release + + button 3 + + release - - - + + + - move > - threshold + + move > + + threshold - - - + + + - IDLE + + IDLE - + - timeout + + timeout - - - + + + - first - finger up + + first + + finger up - - - + + + - IDLE + + IDLE - + - fourth - finger down + + fourth + + finger down - - - - - + + + + + - DRAGGING_OR_DOUBLETAP + + DRAGGING_OR_DOUBLETAP - - - + + + - timeout + + timeout - - - + + + - first - finger up + + first + + finger up - - - + + + - button 1 - release + + button 1 + + release - + - button 1 - press + + button 1 + + press - + - btn1 - release + + btn1 + + release - - - - - - - - - + + + - second - finger down + + second + + finger down - - - + + + - move > - threshold + + move > + + threshold - - - - - + + + + + - HOLD + + HOLD - + - first - finger up + + first + + finger up - - - - - + + + + + - second - finger down + + second + + finger down - - - - - - - + + + + + + + - TOUCH_2_HOLD + + TOUCH_2_HOLD - + - second - finger up + + second + + finger up - - - + + + - first - finger up + + first + + finger up - - - - - - - + + + + + + + - third - finger down + + third + + finger down - - - - - - - + + + + + + + - TOUCH_3_HOLD + + TOUCH_3_HOLD - - - + + + - fourth - finger down + + fourth + + finger down - + - DEAD + + DEAD - - - - - - - + + + + + + + - any finger up + + any finger up - + - fourth - finger up + + fourth + + finger up - + - any finger up + + any finger up - - - - + + + + - - yes + + + yes - + - any finger up + + any finger up - - - - - - - - - + + + + + + + + + - IDLE + + IDLE - + - if finger - count == 0 + + if finger + + count == 0 - - - - - - - + + + + + + + - second - finger up + + second + + finger up - + - DRAGGING_2 + + DRAGGING_2 - - - - - + + + + + - first - finger up + + first + + finger up - - - - - - - + + + + + + + - second - finger down + + second + + finger down - - - - - - - + + + + + + + - third - finger down + + third + + finger down - - - + + + - btn1 - release + + btn1 + + release - - - + + + - clickpad - button - press + + phys + + button + + press - - - - - - - - - - - - - + + + + + + + + + + + + + - clickpad - button - press + + phys + + button + + press - - - + + + - button 1 - release + + button 1 + + release - - - - - - - - - - - + + + + + + + + + + + - DRAGGING_WAIT + + DRAGGING_WAIT - + - timeout + + timeout - - - - - - - + + + + + + + - first - finger down + + first + + finger down - - - + + + - TOUCH_TOUCH + + TOUCH_TOUCH - + - TOUCH_IDLE + + TOUCH_IDLE - - - - - - - - - + + + + + + + + + - TOUCH_DEAD + + TOUCH_DEAD - - - - - - - - - - + + + + + + + + + + - - yes + + + yes - + - TOUCH_DEAD + + TOUCH_DEAD - - - - - - - + + + + + + + - TOUCH_IDLE + + TOUCH_IDLE - - - + + + - TOUCH_TOUCH + + TOUCH_TOUCH - - - - - + + + + + - TOUCH_IDLE + + TOUCH_IDLE - - - + + + - TOUCH_IDLE + + TOUCH_IDLE - - - + + + - TOUCH_IDLE + + TOUCH_IDLE - - - + + + - TOUCH_TOUCH + + TOUCH_TOUCH - - - + + + - that finger - TOUCH_IDLE + + that finger + + TOUCH_IDLE - - - + + + - TOUCH_DEAD + + TOUCH_DEAD - - - - - - - + + + + + + + - that finger - TOUCH_IDLE + + that finger + + TOUCH_IDLE - - - - + + + + - - no + + + no - + - TOUCH_TOUCH + + TOUCH_TOUCH - - - + + + - TOUCH_IDLE + + TOUCH_IDLE - - - + - TOUCH_TOUCH + + TOUCH_TOUCH - - - + + + - TOUCH_DEAD + + TOUCH_DEAD - - - - - + + + + + - TOUCH_IDLE + + TOUCH_IDLE - - - + + + - TOUCH_TOUCH + + TOUCH_TOUCH - - - - - + - TOUCH_TOUCH + + TOUCH_TOUCH - - - + + + - TOUCH_IDLE + + TOUCH_IDLE - - - + + + - TOUCH_IDLE + + TOUCH_IDLE - - - + + + - TOUCH_TOUCH + + TOUCH_TOUCH - - - + + + - TOUCH_IDLE + + TOUCH_IDLE - - - + + + - TOUCH_TOUCH + + TOUCH_TOUCH - - - + + + - that finger - TOUCH_IDLE + + that finger + + TOUCH_IDLE - - - + + + - TOUCH_DEAD + + TOUCH_DEAD - - - + + + - TOUCH_DEAD + + TOUCH_DEAD - + - TOUCH_DEAD + + TOUCH_DEAD - + - TOUCH_DEAD + + TOUCH_DEAD - - - + + + - TOUCH_DEAD + + TOUCH_DEAD - - - + + + - TOUCH_DEAD + + TOUCH_DEAD - - - + + + - state == - TOUCH_TOUCH + + state == + + TOUCH_TOUCH - + - that finger state == - TOUCH_TOUCH + + that finger state == + + TOUCH_TOUCH - - + + - - no + + + no - + - TOUCH_DEAD + + TOUCH_DEAD - - - + + + - TOUCH_DEAD + + TOUCH_DEAD - + - TOUCH_DEAD + + TOUCH_DEAD + + + + first + + finger down + + + + + MULTITAP + + + + + + + + + timeout + + + + + + + + + IDLE + + + + + + + + + + + MULTITAP_DOWN + + + + + button 1 + + press + + + + + + + + + first + + finger up + + + + + + + button 1 + + release + + + + + + + timeout + + + + + second + + finger down + + + + + move > + + threshold + + + + + + + + + + + button 1 + + release + + + + + button 1 + + press + + + + + + + + + + + button 1 + + release + + + + + button 1 + + press + + + + + + + + + + + button 1 + + release + + + + + button 1 + + press + + + + + + + + + + + + + TOUCH_TOUCH + + + + + + + TOUCH_IDLE + + + + + + + phys + + button + + press + + + + + + + + + + + DRAGGING_OR_TAP + + + + + first + + finger up + + + + + + + timeout + + + + + + + + + move > + + threshold + + + + + + + + + + + + + + + TOUCH_IDLE + + + + + + + diff --git a/include/linux/input.h b/include/linux/input.h index 2ac46630..4bf3d6d4 100644 --- a/include/linux/input.h +++ b/include/linux/input.h @@ -162,6 +162,7 @@ struct input_keymap_entry { #define INPUT_PROP_SEMI_MT 0x03 /* touch rectangle only */ #define INPUT_PROP_TOPBUTTONPAD 0x04 /* softbuttons at top of pad */ #define INPUT_PROP_POINTING_STICK 0x05 /* is a pointing stick */ +#define INPUT_PROP_ACCELEROMETER 0x06 /* has accelerometer */ #define INPUT_PROP_MAX 0x1f #define INPUT_PROP_CNT (INPUT_PROP_MAX + 1) @@ -459,7 +460,10 @@ struct input_keymap_entry { #define KEY_VIDEO_NEXT 241 /* drive next video source */ #define KEY_VIDEO_PREV 242 /* drive previous video source */ #define KEY_BRIGHTNESS_CYCLE 243 /* brightness up, after max is min */ -#define KEY_BRIGHTNESS_ZERO 244 /* brightness off, use ambient */ +#define KEY_BRIGHTNESS_AUTO 244 /* Set Auto Brightness: manual + brightness control is off, + rely on ambient */ +#define KEY_BRIGHTNESS_ZERO KEY_BRIGHTNESS_AUTO #define KEY_DISPLAY_OFF 245 /* display device to off state */ #define KEY_WWAN 246 /* Wireless WAN (LTE, UMTS, GSM, etc.) */ @@ -629,6 +633,7 @@ struct input_keymap_entry { #define KEY_ADDRESSBOOK 0x1ad /* AL Contacts/Address Book */ #define KEY_MESSENGER 0x1ae /* AL Instant Messaging */ #define KEY_DISPLAYTOGGLE 0x1af /* Turn display (LCD) on and off */ +#define KEY_BRIGHTNESS_TOGGLE KEY_DISPLAYTOGGLE #define KEY_SPELLCHECK 0x1b0 /* AL Spell Check */ #define KEY_LOGOFF 0x1b1 /* AL Logoff */ @@ -720,6 +725,24 @@ struct input_keymap_entry { #define KEY_ALS_TOGGLE 0x230 /* Ambient light sensor */ +#define KEY_BUTTONCONFIG 0x240 /* AL Button Configuration */ +#define KEY_TASKMANAGER 0x241 /* AL Task/Project Manager */ +#define KEY_JOURNAL 0x242 /* AL Log/Journal/Timecard */ +#define KEY_CONTROLPANEL 0x243 /* AL Control Panel */ +#define KEY_APPSELECT 0x244 /* AL Select Task/Application */ +#define KEY_SCREENSAVER 0x245 /* AL Screen Saver */ +#define KEY_VOICECOMMAND 0x246 /* Listening Voice Command */ + +#define KEY_BRIGHTNESS_MIN 0x250 /* Set Brightness to Minimum */ +#define KEY_BRIGHTNESS_MAX 0x251 /* Set Brightness to Maximum */ + +#define KEY_KBDINPUTASSIST_PREV 0x260 +#define KEY_KBDINPUTASSIST_NEXT 0x261 +#define KEY_KBDINPUTASSIST_PREVGROUP 0x262 +#define KEY_KBDINPUTASSIST_NEXTGROUP 0x263 +#define KEY_KBDINPUTASSIST_ACCEPT 0x264 +#define KEY_KBDINPUTASSIST_CANCEL 0x265 + #define BTN_TRIGGER_HAPPY 0x2c0 #define BTN_TRIGGER_HAPPY1 0x2c0 #define BTN_TRIGGER_HAPPY2 0x2c1 @@ -945,7 +968,8 @@ struct input_keymap_entry { */ #define MT_TOOL_FINGER 0 #define MT_TOOL_PEN 1 -#define MT_TOOL_MAX 1 +#define MT_TOOL_PALM 2 +#define MT_TOOL_MAX 2 /* * Values describing the status of a force-feedback effect diff --git a/src/Makefile.am b/src/Makefile.am index d5cd4f41..343e75c7 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,5 +1,6 @@ lib_LTLIBRARIES = libinput.la -noinst_LTLIBRARIES = libinput-util.la +noinst_LTLIBRARIES = libinput-util.la \ + libfilter.la include_HEADERS = \ libinput.h @@ -10,14 +11,15 @@ libinput_la_SOURCES = \ libinput-private.h \ evdev.c \ evdev.h \ - evdev-tablet.c \ - evdev-tablet.h \ + evdev-middle-button.c \ evdev-mt-touchpad.c \ evdev-mt-touchpad.h \ evdev-mt-touchpad-tap.c \ evdev-mt-touchpad-buttons.c \ evdev-mt-touchpad-edge-scroll.c \ evdev-mt-touchpad-gestures.c \ + evdev-tablet.c \ + evdev-tablet.h \ filter.c \ filter.h \ filter-private.h \ @@ -52,6 +54,13 @@ libinput_util_la_CFLAGS = -I$(top_srcdir)/include \ $(LIBUDEV_CFLAGS) \ $(GCC_CFLAGS) +libfilter_la_SOURCES = \ + filter.c \ + filter.h \ + filter-private.h +libfilter_la_LIBADD = +libfilter_la_CFLAGS = + libinput_la_LDFLAGS = -version-info $(LIBINPUT_LT_VERSION) -shared \ -Wl,--version-script=$(srcdir)/libinput.sym diff --git a/src/evdev-middle-button.c b/src/evdev-middle-button.c new file mode 100644 index 00000000..328cf6c3 --- /dev/null +++ b/src/evdev-middle-button.c @@ -0,0 +1,716 @@ +/* + * Copyright © 2014 Red Hat, Inc. + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include "evdev.h" + +#define MIDDLEBUTTON_TIMEOUT 50 + +/***************************************** + * BEFORE YOU EDIT THIS FILE, look at the state diagram in + * doc/middle-button-emulation-state-machine.svg, or online at + * https://drive.google.com/file/d/0B1NwWmji69nodUJncXRMc1FvY1k/view?usp=sharing + * (it's a http://draw.io diagram) + * + * Any changes in this file must be represented in the diagram. + * + * Note in regards to the state machine: it only handles left, right and + * emulated middle button clicks, all other button events are passed + * through. When in the PASSTHROUGH state, all events are passed through + * as-is. + */ + +#define CASE_RETURN_STRING(a) case a: return #a; + +static inline const char* +middlebutton_state_to_str(enum evdev_middlebutton_state state) +{ + switch (state) { + CASE_RETURN_STRING(MIDDLEBUTTON_IDLE); + CASE_RETURN_STRING(MIDDLEBUTTON_LEFT_DOWN); + CASE_RETURN_STRING(MIDDLEBUTTON_RIGHT_DOWN); + CASE_RETURN_STRING(MIDDLEBUTTON_MIDDLE); + CASE_RETURN_STRING(MIDDLEBUTTON_LEFT_UP_PENDING); + CASE_RETURN_STRING(MIDDLEBUTTON_RIGHT_UP_PENDING); + CASE_RETURN_STRING(MIDDLEBUTTON_PASSTHROUGH); + CASE_RETURN_STRING(MIDDLEBUTTON_IGNORE_LR); + CASE_RETURN_STRING(MIDDLEBUTTON_IGNORE_L); + CASE_RETURN_STRING(MIDDLEBUTTON_IGNORE_R); + } + + return NULL; +} + +static inline const char* +middlebutton_event_to_str(enum evdev_middlebutton_event event) +{ + switch (event) { + CASE_RETURN_STRING(MIDDLEBUTTON_EVENT_L_DOWN); + CASE_RETURN_STRING(MIDDLEBUTTON_EVENT_R_DOWN); + CASE_RETURN_STRING(MIDDLEBUTTON_EVENT_OTHER); + CASE_RETURN_STRING(MIDDLEBUTTON_EVENT_L_UP); + CASE_RETURN_STRING(MIDDLEBUTTON_EVENT_R_UP); + CASE_RETURN_STRING(MIDDLEBUTTON_EVENT_TIMEOUT); + CASE_RETURN_STRING(MIDDLEBUTTON_EVENT_ALL_UP); + } + + return NULL; +} + +static void +middlebutton_state_error(struct evdev_device *device, + enum evdev_middlebutton_event event) +{ + log_bug_libinput(device->base.seat->libinput, + "Invalid event %s in middle btn state %s\n", + middlebutton_event_to_str(event), + middlebutton_state_to_str(device->middlebutton.state)); +} + +static void +middlebutton_timer_set(struct evdev_device *device, uint64_t now) +{ + libinput_timer_set(&device->middlebutton.timer, + now + MIDDLEBUTTON_TIMEOUT); +} + +static void +middlebutton_timer_cancel(struct evdev_device *device) +{ + libinput_timer_cancel(&device->middlebutton.timer); +} + +static inline void +middlebutton_set_state(struct evdev_device *device, + enum evdev_middlebutton_state state, + uint64_t now) +{ + switch (state) { + case MIDDLEBUTTON_LEFT_DOWN: + case MIDDLEBUTTON_RIGHT_DOWN: + middlebutton_timer_set(device, now); + device->middlebutton.first_event_time = now; + break; + case MIDDLEBUTTON_IDLE: + case MIDDLEBUTTON_MIDDLE: + case MIDDLEBUTTON_LEFT_UP_PENDING: + case MIDDLEBUTTON_RIGHT_UP_PENDING: + case MIDDLEBUTTON_PASSTHROUGH: + case MIDDLEBUTTON_IGNORE_LR: + case MIDDLEBUTTON_IGNORE_L: + case MIDDLEBUTTON_IGNORE_R: + middlebutton_timer_cancel(device); + break; + } + + device->middlebutton.state = state; +} + +static void +middlebutton_post_event(struct evdev_device *device, + uint64_t now, + int button, + enum libinput_button_state state) +{ + evdev_pointer_notify_button(device, + now, + button, + state); +} + +static int +evdev_middlebutton_idle_handle_event(struct evdev_device *device, + uint64_t time, + enum evdev_middlebutton_event event) +{ + switch (event) { + case MIDDLEBUTTON_EVENT_L_DOWN: + middlebutton_set_state(device, MIDDLEBUTTON_LEFT_DOWN, time); + break; + case MIDDLEBUTTON_EVENT_R_DOWN: + middlebutton_set_state(device, MIDDLEBUTTON_RIGHT_DOWN, time); + break; + case MIDDLEBUTTON_EVENT_OTHER: + return 0; + case MIDDLEBUTTON_EVENT_R_UP: + case MIDDLEBUTTON_EVENT_L_UP: + case MIDDLEBUTTON_EVENT_TIMEOUT: + middlebutton_state_error(device, event); + break; + case MIDDLEBUTTON_EVENT_ALL_UP: + break; + } + + return 1; +} + +static int +evdev_middlebutton_ldown_handle_event(struct evdev_device *device, + uint64_t time, + enum evdev_middlebutton_event event) +{ + switch (event) { + case MIDDLEBUTTON_EVENT_L_DOWN: + middlebutton_state_error(device, event); + break; + case MIDDLEBUTTON_EVENT_R_DOWN: + middlebutton_post_event(device, time, + BTN_MIDDLE, + LIBINPUT_BUTTON_STATE_PRESSED); + middlebutton_set_state(device, MIDDLEBUTTON_MIDDLE, time); + break; + case MIDDLEBUTTON_EVENT_OTHER: + middlebutton_post_event(device, time, + BTN_LEFT, + LIBINPUT_BUTTON_STATE_PRESSED); + middlebutton_set_state(device, + MIDDLEBUTTON_PASSTHROUGH, + time); + return 0; + case MIDDLEBUTTON_EVENT_R_UP: + middlebutton_state_error(device, event); + break; + case MIDDLEBUTTON_EVENT_L_UP: + middlebutton_post_event(device, + device->middlebutton.first_event_time, + BTN_LEFT, + LIBINPUT_BUTTON_STATE_PRESSED); + middlebutton_post_event(device, time, + BTN_LEFT, + LIBINPUT_BUTTON_STATE_RELEASED); + middlebutton_set_state(device, MIDDLEBUTTON_IDLE, time); + break; + case MIDDLEBUTTON_EVENT_TIMEOUT: + middlebutton_post_event(device, + device->middlebutton.first_event_time, + BTN_LEFT, + LIBINPUT_BUTTON_STATE_PRESSED); + middlebutton_set_state(device, + MIDDLEBUTTON_PASSTHROUGH, + time); + break; + case MIDDLEBUTTON_EVENT_ALL_UP: + middlebutton_state_error(device, event); + break; + } + + return 1; +} + +static int +evdev_middlebutton_rdown_handle_event(struct evdev_device *device, + uint64_t time, + enum evdev_middlebutton_event event) +{ + switch (event) { + case MIDDLEBUTTON_EVENT_L_DOWN: + middlebutton_post_event(device, time, + BTN_MIDDLE, + LIBINPUT_BUTTON_STATE_PRESSED); + middlebutton_set_state(device, MIDDLEBUTTON_MIDDLE, time); + break; + case MIDDLEBUTTON_EVENT_R_DOWN: + middlebutton_state_error(device, event); + break; + case MIDDLEBUTTON_EVENT_OTHER: + middlebutton_post_event(device, + device->middlebutton.first_event_time, + BTN_RIGHT, + LIBINPUT_BUTTON_STATE_PRESSED); + middlebutton_set_state(device, + MIDDLEBUTTON_PASSTHROUGH, + time); + return 0; + case MIDDLEBUTTON_EVENT_R_UP: + middlebutton_post_event(device, + device->middlebutton.first_event_time, + BTN_RIGHT, + LIBINPUT_BUTTON_STATE_PRESSED); + middlebutton_post_event(device, time, + BTN_RIGHT, + LIBINPUT_BUTTON_STATE_RELEASED); + middlebutton_set_state(device, MIDDLEBUTTON_IDLE, time); + break; + case MIDDLEBUTTON_EVENT_L_UP: + middlebutton_state_error(device, event); + break; + case MIDDLEBUTTON_EVENT_TIMEOUT: + middlebutton_post_event(device, + device->middlebutton.first_event_time, + BTN_RIGHT, + LIBINPUT_BUTTON_STATE_PRESSED); + middlebutton_set_state(device, + MIDDLEBUTTON_PASSTHROUGH, + time); + break; + case MIDDLEBUTTON_EVENT_ALL_UP: + middlebutton_state_error(device, event); + break; + } + + return 1; +} + +static int +evdev_middlebutton_middle_handle_event(struct evdev_device *device, + uint64_t time, + enum evdev_middlebutton_event event) +{ + switch (event) { + case MIDDLEBUTTON_EVENT_L_DOWN: + case MIDDLEBUTTON_EVENT_R_DOWN: + middlebutton_state_error(device, event); + break; + case MIDDLEBUTTON_EVENT_OTHER: + middlebutton_post_event(device, time, + BTN_MIDDLE, + LIBINPUT_BUTTON_STATE_RELEASED); + middlebutton_set_state(device, MIDDLEBUTTON_IGNORE_LR, time); + return 0; + case MIDDLEBUTTON_EVENT_R_UP: + middlebutton_post_event(device, time, + BTN_MIDDLE, + LIBINPUT_BUTTON_STATE_RELEASED); + middlebutton_set_state(device, + MIDDLEBUTTON_LEFT_UP_PENDING, + time); + break; + case MIDDLEBUTTON_EVENT_L_UP: + middlebutton_post_event(device, time, + BTN_MIDDLE, + LIBINPUT_BUTTON_STATE_RELEASED); + middlebutton_set_state(device, + MIDDLEBUTTON_RIGHT_UP_PENDING, + time); + break; + case MIDDLEBUTTON_EVENT_TIMEOUT: + middlebutton_state_error(device, event); + break; + case MIDDLEBUTTON_EVENT_ALL_UP: + middlebutton_state_error(device, event); + break; + } + + return 1; +} + +static int +evdev_middlebutton_lup_pending_handle_event(struct evdev_device *device, + uint64_t time, + enum evdev_middlebutton_event event) +{ + switch (event) { + case MIDDLEBUTTON_EVENT_L_DOWN: + middlebutton_state_error(device, event); + break; + case MIDDLEBUTTON_EVENT_R_DOWN: + middlebutton_post_event(device, time, + BTN_MIDDLE, + LIBINPUT_BUTTON_STATE_PRESSED); + middlebutton_set_state(device, MIDDLEBUTTON_MIDDLE, time); + break; + case MIDDLEBUTTON_EVENT_OTHER: + middlebutton_set_state(device, MIDDLEBUTTON_IGNORE_L, time); + return 0; + case MIDDLEBUTTON_EVENT_R_UP: + middlebutton_state_error(device, event); + break; + case MIDDLEBUTTON_EVENT_L_UP: + middlebutton_set_state(device, MIDDLEBUTTON_IDLE, time); + break; + case MIDDLEBUTTON_EVENT_TIMEOUT: + middlebutton_state_error(device, event); + break; + case MIDDLEBUTTON_EVENT_ALL_UP: + middlebutton_state_error(device, event); + break; + } + + return 1; +} + +static int +evdev_middlebutton_rup_pending_handle_event(struct evdev_device *device, + uint64_t time, + enum evdev_middlebutton_event event) +{ + switch (event) { + case MIDDLEBUTTON_EVENT_L_DOWN: + middlebutton_post_event(device, time, + BTN_MIDDLE, + LIBINPUT_BUTTON_STATE_PRESSED); + middlebutton_set_state(device, MIDDLEBUTTON_MIDDLE, time); + break; + case MIDDLEBUTTON_EVENT_R_DOWN: + middlebutton_state_error(device, event); + break; + case MIDDLEBUTTON_EVENT_OTHER: + middlebutton_set_state(device, MIDDLEBUTTON_IGNORE_R, time); + return 0; + case MIDDLEBUTTON_EVENT_R_UP: + middlebutton_set_state(device, MIDDLEBUTTON_IDLE, time); + break; + case MIDDLEBUTTON_EVENT_L_UP: + middlebutton_state_error(device, event); + break; + case MIDDLEBUTTON_EVENT_TIMEOUT: + middlebutton_state_error(device, event); + break; + case MIDDLEBUTTON_EVENT_ALL_UP: + middlebutton_state_error(device, event); + break; + } + + return 1; +} + +static int +evdev_middlebutton_passthrough_handle_event(struct evdev_device *device, + uint64_t time, + enum evdev_middlebutton_event event) +{ + switch (event) { + case MIDDLEBUTTON_EVENT_L_DOWN: + case MIDDLEBUTTON_EVENT_R_DOWN: + case MIDDLEBUTTON_EVENT_OTHER: + case MIDDLEBUTTON_EVENT_R_UP: + case MIDDLEBUTTON_EVENT_L_UP: + return 0; + case MIDDLEBUTTON_EVENT_TIMEOUT: + middlebutton_state_error(device, event); + break; + case MIDDLEBUTTON_EVENT_ALL_UP: + middlebutton_set_state(device, MIDDLEBUTTON_IDLE, time); + break; + } + + return 1; +} + +static int +evdev_middlebutton_ignore_lr_handle_event(struct evdev_device *device, + uint64_t time, + enum evdev_middlebutton_event event) +{ + switch (event) { + case MIDDLEBUTTON_EVENT_L_DOWN: + case MIDDLEBUTTON_EVENT_R_DOWN: + middlebutton_state_error(device, event); + break; + case MIDDLEBUTTON_EVENT_OTHER: + return 0; + case MIDDLEBUTTON_EVENT_R_UP: + middlebutton_set_state(device, MIDDLEBUTTON_IGNORE_L, time); + break; + case MIDDLEBUTTON_EVENT_L_UP: + middlebutton_set_state(device, MIDDLEBUTTON_IGNORE_R, time); + break; + case MIDDLEBUTTON_EVENT_TIMEOUT: + middlebutton_state_error(device, event); + break; + case MIDDLEBUTTON_EVENT_ALL_UP: + middlebutton_state_error(device, event); + break; + } + + return 1; +} + +static int +evdev_middlebutton_ignore_l_handle_event(struct evdev_device *device, + uint64_t time, + enum evdev_middlebutton_event event) +{ + switch (event) { + case MIDDLEBUTTON_EVENT_L_DOWN: + middlebutton_state_error(device, event); + break; + case MIDDLEBUTTON_EVENT_R_DOWN: + return 0; + case MIDDLEBUTTON_EVENT_OTHER: + case MIDDLEBUTTON_EVENT_R_UP: + return 0; + case MIDDLEBUTTON_EVENT_L_UP: + middlebutton_set_state(device, + MIDDLEBUTTON_PASSTHROUGH, + time); + break; + case MIDDLEBUTTON_EVENT_TIMEOUT: + case MIDDLEBUTTON_EVENT_ALL_UP: + middlebutton_state_error(device, event); + break; + } + + return 1; +} +static int +evdev_middlebutton_ignore_r_handle_event(struct evdev_device *device, + uint64_t time, + enum evdev_middlebutton_event event) +{ + switch (event) { + case MIDDLEBUTTON_EVENT_L_DOWN: + return 0; + case MIDDLEBUTTON_EVENT_R_DOWN: + middlebutton_state_error(device, event); + break; + case MIDDLEBUTTON_EVENT_OTHER: + return 0; + case MIDDLEBUTTON_EVENT_R_UP: + middlebutton_set_state(device, + MIDDLEBUTTON_PASSTHROUGH, + time); + break; + case MIDDLEBUTTON_EVENT_L_UP: + return 0; + case MIDDLEBUTTON_EVENT_TIMEOUT: + case MIDDLEBUTTON_EVENT_ALL_UP: + break; + } + + return 1; +} + +static int +evdev_middlebutton_handle_event(struct evdev_device *device, + uint64_t time, + enum evdev_middlebutton_event event) +{ + int rc; + enum evdev_middlebutton_state current; + + current = device->middlebutton.state; + + switch (current) { + case MIDDLEBUTTON_IDLE: + rc = evdev_middlebutton_idle_handle_event(device, time, event); + break; + case MIDDLEBUTTON_LEFT_DOWN: + rc = evdev_middlebutton_ldown_handle_event(device, time, event); + break; + case MIDDLEBUTTON_RIGHT_DOWN: + rc = evdev_middlebutton_rdown_handle_event(device, time, event); + break; + case MIDDLEBUTTON_MIDDLE: + rc = evdev_middlebutton_middle_handle_event(device, time, event); + break; + case MIDDLEBUTTON_LEFT_UP_PENDING: + rc = evdev_middlebutton_lup_pending_handle_event(device, + time, + event); + break; + case MIDDLEBUTTON_RIGHT_UP_PENDING: + rc = evdev_middlebutton_rup_pending_handle_event(device, + time, + event); + break; + case MIDDLEBUTTON_PASSTHROUGH: + rc = evdev_middlebutton_passthrough_handle_event(device, + time, + event); + break; + case MIDDLEBUTTON_IGNORE_LR: + rc = evdev_middlebutton_ignore_lr_handle_event(device, + time, + event); + break; + case MIDDLEBUTTON_IGNORE_L: + rc = evdev_middlebutton_ignore_l_handle_event(device, + time, + event); + break; + case MIDDLEBUTTON_IGNORE_R: + rc = evdev_middlebutton_ignore_r_handle_event(device, + time, + event); + break; + } + + log_debug(device->base.seat->libinput, + "middlebuttonstate: %s → %s → %s, rc %d\n", + middlebutton_state_to_str(current), + middlebutton_event_to_str(event), + middlebutton_state_to_str(device->middlebutton.state), + rc); + + return rc; +} + +static inline void +evdev_middlebutton_apply_config(struct evdev_device *device) +{ + if (device->middlebutton.want_enabled == + device->middlebutton.enabled) + return; + + if (device->middlebutton.button_mask != 0) + return; + + device->middlebutton.enabled = device->middlebutton.want_enabled; +} + +bool +evdev_middlebutton_filter_button(struct evdev_device *device, + uint64_t time, + int button, + enum libinput_button_state state) +{ + enum evdev_middlebutton_event event; + bool is_press = state == LIBINPUT_BUTTON_STATE_PRESSED; + int rc; + unsigned int bit = (button - BTN_LEFT); + uint32_t old_mask = 0; + + if (!device->middlebutton.enabled) + return false; + + switch (button) { + case BTN_LEFT: + if (is_press) + event = MIDDLEBUTTON_EVENT_L_DOWN; + else + event = MIDDLEBUTTON_EVENT_L_UP; + break; + case BTN_RIGHT: + if (is_press) + event = MIDDLEBUTTON_EVENT_R_DOWN; + else + event = MIDDLEBUTTON_EVENT_R_UP; + break; + + /* BTN_MIDDLE counts as "other" and resets middle button + * emulation */ + case BTN_MIDDLE: + default: + event = MIDDLEBUTTON_EVENT_OTHER; + break; + } + + if (button < BTN_LEFT || + bit >= sizeof(device->middlebutton.button_mask) * 8) { + log_bug_libinput(device->base.seat->libinput, + "Button mask too small for %d\n", + libevdev_event_code_get_name(EV_KEY, + button)); + return true; + } + + rc = evdev_middlebutton_handle_event(device, time, event); + + old_mask = device->middlebutton.button_mask; + if (is_press) + device->middlebutton.button_mask |= 1 << bit; + else + device->middlebutton.button_mask &= ~(1 << bit); + + if (old_mask != device->middlebutton.button_mask && + device->middlebutton.button_mask == 0) { + evdev_middlebutton_handle_event(device, + time, + MIDDLEBUTTON_EVENT_ALL_UP); + evdev_middlebutton_apply_config(device); + } + + return rc; +} + +static void +evdev_middlebutton_handle_timeout(uint64_t now, void *data) +{ + struct evdev_device *device = (struct evdev_device*)data; + + evdev_middlebutton_handle_event(device, + libinput_now(device->base.seat->libinput), + MIDDLEBUTTON_EVENT_TIMEOUT); +} + +static int +evdev_middlebutton_is_available(struct libinput_device *device) +{ + return 1; +} + +static enum libinput_config_status +evdev_middlebutton_set(struct libinput_device *device, + enum libinput_config_middle_emulation_state enable) +{ + struct evdev_device *evdev = (struct evdev_device*)device; + + switch (enable) { + case LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED: + evdev->middlebutton.want_enabled = true; + break; + case LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED: + evdev->middlebutton.want_enabled = false; + break; + default: + return LIBINPUT_CONFIG_STATUS_INVALID; + } + + evdev_middlebutton_apply_config(evdev); + + return LIBINPUT_CONFIG_STATUS_SUCCESS; +} + +static enum libinput_config_middle_emulation_state +evdev_middlebutton_get(struct libinput_device *device) +{ + struct evdev_device *evdev = (struct evdev_device*)device; + + return evdev->middlebutton.enabled ? + LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED : + LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED; +} + +static enum libinput_config_middle_emulation_state +evdev_middlebutton_get_default(struct libinput_device *device) +{ + struct evdev_device *evdev = (struct evdev_device*)device; + + return evdev->middlebutton.enabled_default ? + LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED : + LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED; +} + +void +evdev_init_middlebutton(struct evdev_device *device, + bool enable, + bool want_config) +{ + libinput_timer_init(&device->middlebutton.timer, + device->base.seat->libinput, + evdev_middlebutton_handle_timeout, + device); + device->middlebutton.enabled_default = enable; + device->middlebutton.want_enabled = enable; + device->middlebutton.enabled = enable; + + if (!want_config) + return; + + device->middlebutton.config.available = evdev_middlebutton_is_available; + device->middlebutton.config.set = evdev_middlebutton_set; + device->middlebutton.config.get = evdev_middlebutton_get; + device->middlebutton.config.get_default = evdev_middlebutton_get_default; + device->base.config.middle_emulation = &device->middlebutton.config; +} diff --git a/src/evdev-mt-touchpad-buttons.c b/src/evdev-mt-touchpad-buttons.c index 18c32fda..43e983b2 100644 --- a/src/evdev-mt-touchpad-buttons.c +++ b/src/evdev-mt-touchpad-buttons.c @@ -144,12 +144,15 @@ tp_button_set_leave_timer(struct tp_dispatch *tp, struct tp_touch *t) * as described in the state machine diagram. */ static void -tp_button_set_state(struct tp_dispatch *tp, struct tp_touch *t, - enum button_state new_state, enum button_event event) +tp_button_set_state(struct tp_dispatch *tp, + struct tp_touch *t, + enum button_state new_state, + enum button_event event) { libinput_timer_cancel(&t->button.timer); t->button.state = new_state; + switch (t->button.state) { case BUTTON_STATE_NONE: t->button.curr = 0; @@ -235,7 +238,9 @@ tp_button_bottom_handle_event(struct tp_dispatch *tp, case BUTTON_EVENT_IN_BOTTOM_R: case BUTTON_EVENT_IN_BOTTOM_L: if (event != t->button.curr) - tp_button_set_state(tp, t, BUTTON_STATE_BOTTOM, + tp_button_set_state(tp, + t, + BUTTON_STATE_BOTTOM, event); break; case BUTTON_EVENT_IN_TOP_R: @@ -256,8 +261,8 @@ tp_button_bottom_handle_event(struct tp_dispatch *tp, static void tp_button_top_handle_event(struct tp_dispatch *tp, - struct tp_touch *t, - enum button_event event) + struct tp_touch *t, + enum button_event event) { switch (event) { case BUTTON_EVENT_IN_BOTTOM_R: @@ -268,7 +273,9 @@ tp_button_top_handle_event(struct tp_dispatch *tp, case BUTTON_EVENT_IN_TOP_M: case BUTTON_EVENT_IN_TOP_L: if (event != t->button.curr) - tp_button_set_state(tp, t, BUTTON_STATE_TOP_NEW, + tp_button_set_state(tp, + t, + BUTTON_STATE_TOP_NEW, event); break; case BUTTON_EVENT_IN_AREA: @@ -286,8 +293,8 @@ tp_button_top_handle_event(struct tp_dispatch *tp, static void tp_button_top_new_handle_event(struct tp_dispatch *tp, - struct tp_touch *t, - enum button_event event) + struct tp_touch *t, + enum button_event event) { switch(event) { case BUTTON_EVENT_IN_BOTTOM_R: @@ -298,7 +305,9 @@ tp_button_top_new_handle_event(struct tp_dispatch *tp, case BUTTON_EVENT_IN_TOP_M: case BUTTON_EVENT_IN_TOP_L: if (event != t->button.curr) - tp_button_set_state(tp, t, BUTTON_STATE_TOP_NEW, + tp_button_set_state(tp, + t, + BUTTON_STATE_TOP_NEW, event); break; case BUTTON_EVENT_IN_AREA: @@ -320,18 +329,22 @@ tp_button_top_new_handle_event(struct tp_dispatch *tp, static void tp_button_top_to_ignore_handle_event(struct tp_dispatch *tp, - struct tp_touch *t, - enum button_event event) + struct tp_touch *t, + enum button_event event) { switch(event) { case BUTTON_EVENT_IN_TOP_R: case BUTTON_EVENT_IN_TOP_M: case BUTTON_EVENT_IN_TOP_L: if (event == t->button.curr) - tp_button_set_state(tp, t, BUTTON_STATE_TOP, + tp_button_set_state(tp, + t, + BUTTON_STATE_TOP, event); else - tp_button_set_state(tp, t, BUTTON_STATE_TOP_NEW, + tp_button_set_state(tp, + t, + BUTTON_STATE_TOP_NEW, event); break; case BUTTON_EVENT_IN_BOTTOM_R: @@ -352,8 +365,8 @@ tp_button_top_to_ignore_handle_event(struct tp_dispatch *tp, static void tp_button_ignore_handle_event(struct tp_dispatch *tp, - struct tp_touch *t, - enum button_event event) + struct tp_touch *t, + enum button_event event) { switch (event) { case BUTTON_EVENT_IN_BOTTOM_R: @@ -426,18 +439,22 @@ tp_button_handle_state(struct tp_dispatch *tp, uint64_t time) if (t->state == TOUCH_END) { tp_button_handle_event(tp, t, BUTTON_EVENT_UP, time); } else if (t->dirty) { + enum button_event event; + if (is_inside_bottom_right_area(tp, t)) - tp_button_handle_event(tp, t, BUTTON_EVENT_IN_BOTTOM_R, time); + event = BUTTON_EVENT_IN_BOTTOM_R; else if (is_inside_bottom_left_area(tp, t)) - tp_button_handle_event(tp, t, BUTTON_EVENT_IN_BOTTOM_L, time); + event = BUTTON_EVENT_IN_BOTTOM_L; else if (is_inside_top_right_area(tp, t)) - tp_button_handle_event(tp, t, BUTTON_EVENT_IN_TOP_R, time); + event = BUTTON_EVENT_IN_TOP_R; else if (is_inside_top_middle_area(tp, t)) - tp_button_handle_event(tp, t, BUTTON_EVENT_IN_TOP_M, time); + event = BUTTON_EVENT_IN_TOP_M; else if (is_inside_top_left_area(tp, t)) - tp_button_handle_event(tp, t, BUTTON_EVENT_IN_TOP_L, time); + event = BUTTON_EVENT_IN_TOP_L; else - tp_button_handle_event(tp, t, BUTTON_EVENT_IN_AREA, time); + event = BUTTON_EVENT_IN_AREA; + + tp_button_handle_event(tp, t, event, time); } if (tp->queued & TOUCHPAD_EVENT_BUTTON_RELEASE) tp_button_handle_event(tp, t, BUTTON_EVENT_RELEASE, time); @@ -629,12 +646,25 @@ tp_button_config_click_get_method(struct libinput_device *device) static enum libinput_config_click_method tp_click_get_default_method(struct tp_dispatch *tp) { + struct evdev_device *device = tp->device; + if (!tp->buttons.is_clickpad) return LIBINPUT_CONFIG_CLICK_METHOD_NONE; else if (libevdev_get_id_vendor(tp->device->evdev) == VENDOR_ID_APPLE) return LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER; - else - return LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS; + + switch (device->model) { + case EVDEV_MODEL_CHROMEBOOK: + case EVDEV_MODEL_SYSTEM76_BONOBO: + case EVDEV_MODEL_SYSTEM76_GALAGO: + case EVDEV_MODEL_SYSTEM76_KUDU: + case EVDEV_MODEL_CLEVO_W740SU: + return LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER; + default: + break; + } + + return LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS; } static enum libinput_config_click_method @@ -694,6 +724,10 @@ tp_init_buttons(struct tp_dispatch *tp, tp_init_top_softbuttons(tp, device, 1.0); + if (!tp->buttons.is_clickpad && + !libevdev_has_event_code(device->evdev, EV_KEY, BTN_MIDDLE)) + evdev_init_middlebutton(tp->device, true, false); + tp_for_each_touch(tp, t) { t->button.state = BUTTON_STATE_NONE; libinput_timer_init(&t->button.timer, @@ -734,10 +768,10 @@ tp_post_physical_buttons(struct tp_dispatch *tp, uint64_t time) state = LIBINPUT_BUTTON_STATE_RELEASED; b = evdev_to_left_handed(tp->device, button); - evdev_pointer_notify_button(tp->device, - time, - b, - state); + evdev_pointer_notify_physical_button(tp->device, + time, + b, + state); } button++; @@ -765,8 +799,10 @@ tp_notify_clickpadbutton(struct tp_dispatch *tp, event.type = EV_KEY; event.code = button; event.value = (state == LIBINPUT_BUTTON_STATE_PRESSED) ? 1 : 0; - dispatch->interface->process(dispatch, tp->buttons.trackpoint, - &event, time); + dispatch->interface->process(dispatch, + tp->buttons.trackpoint, + &event, + time); return 1; } @@ -779,8 +815,9 @@ tp_notify_clickpadbutton(struct tp_dispatch *tp, * by the softbutton code with one based on the number of fingers. */ if (tp->buttons.click_method == LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER && - state == LIBINPUT_BUTTON_STATE_PRESSED) { + state == LIBINPUT_BUTTON_STATE_PRESSED) { switch (tp->nfingers_down) { + case 0: case 1: button = BTN_LEFT; break; case 2: button = BTN_RIGHT; break; case 3: button = BTN_MIDDLE; break; @@ -814,46 +851,48 @@ tp_post_clickpadbutton_buttons(struct tp_dispatch *tp, uint64_t time) if (current) { struct tp_touch *t; + uint32_t area = 0; tp_for_each_touch(tp, t) { switch (t->button.curr) { case BUTTON_EVENT_IN_AREA: - button |= AREA; + area |= AREA; break; case BUTTON_EVENT_IN_TOP_L: is_top = 1; /* fallthrough */ case BUTTON_EVENT_IN_BOTTOM_L: - button |= LEFT; + area |= LEFT; break; case BUTTON_EVENT_IN_TOP_M: is_top = 1; - button |= MIDDLE; + area |= MIDDLE; break; case BUTTON_EVENT_IN_TOP_R: is_top = 1; /* fallthrough */ case BUTTON_EVENT_IN_BOTTOM_R: - button |= RIGHT; + area |= RIGHT; break; default: break; } } - if (button == 0) { + if (area == 0 && + tp->buttons.click_method != LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER) { /* No touches, wait for a touch before processing */ tp->buttons.click_pending = true; return 0; } - if ((button & MIDDLE) || ((button & LEFT) && (button & RIGHT))) + if ((area & MIDDLE) || ((area & LEFT) && (area & RIGHT))) button = evdev_to_left_handed(tp->device, BTN_MIDDLE); - else if (button & RIGHT) + else if (area & RIGHT) button = evdev_to_left_handed(tp->device, BTN_RIGHT); - else if (button & LEFT) + else if (area & LEFT) button = evdev_to_left_handed(tp->device, BTN_LEFT); - else /* main area is always BTN_LEFT */ + else /* main or no area (for clickfinger) is always BTN_LEFT */ button = BTN_LEFT; tp->buttons.active = button; @@ -870,8 +909,11 @@ tp_post_clickpadbutton_buttons(struct tp_dispatch *tp, uint64_t time) tp->buttons.click_pending = false; if (button) - return tp_notify_clickpadbutton(tp, time, button, is_top, state); - + return tp_notify_clickpadbutton(tp, + time, + button, + is_top, + state); return 0; } @@ -893,5 +935,6 @@ tp_button_touch_active(struct tp_dispatch *tp, struct tp_touch *t) bool tp_button_is_inside_softbutton_area(struct tp_dispatch *tp, struct tp_touch *t) { - return is_inside_top_button_area(tp, t) || is_inside_bottom_button_area(tp, t); + return is_inside_top_button_area(tp, t) || + is_inside_bottom_button_area(tp, t); } diff --git a/src/evdev-mt-touchpad-edge-scroll.c b/src/evdev-mt-touchpad-edge-scroll.c index 26d6bbcf..369fdedc 100644 --- a/src/evdev-mt-touchpad-edge-scroll.c +++ b/src/evdev-mt-touchpad-edge-scroll.c @@ -29,6 +29,8 @@ #include "evdev-mt-touchpad.h" +#define CASE_RETURN_STRING(a) case a: return #a + #define DEFAULT_SCROLL_LOCK_TIMEOUT 300 /* ms */ /* Use a reasonably large threshold until locked into scrolling mode, to avoid accidentally locking in scrolling mode when trying to use the entire @@ -44,6 +46,32 @@ enum scroll_event { SCROLL_EVENT_POSTED, }; +static inline const char* +edge_state_to_str(enum tp_edge_scroll_touch_state state) +{ + + switch (state) { + CASE_RETURN_STRING(EDGE_SCROLL_TOUCH_STATE_NONE); + CASE_RETURN_STRING(EDGE_SCROLL_TOUCH_STATE_EDGE_NEW); + CASE_RETURN_STRING(EDGE_SCROLL_TOUCH_STATE_EDGE); + CASE_RETURN_STRING(EDGE_SCROLL_TOUCH_STATE_AREA); + } + return NULL; +} + +static inline const char* +edge_event_to_str(enum scroll_event event) +{ + switch (event) { + CASE_RETURN_STRING(SCROLL_EVENT_TOUCH); + CASE_RETURN_STRING(SCROLL_EVENT_MOTION); + CASE_RETURN_STRING(SCROLL_EVENT_RELEASE); + CASE_RETURN_STRING(SCROLL_EVENT_TIMEOUT); + CASE_RETURN_STRING(SCROLL_EVENT_POSTED); + } + return NULL; +} + static uint32_t tp_touch_get_edge(struct tp_dispatch *tp, struct tp_touch *t) { @@ -204,7 +232,10 @@ tp_edge_scroll_handle_event(struct tp_dispatch *tp, struct tp_touch *t, enum scroll_event event) { - switch (t->scroll.edge_state) { + struct libinput *libinput = tp->device->base.seat->libinput; + enum tp_edge_scroll_touch_state current = t->scroll.edge_state; + + switch (current) { case EDGE_SCROLL_TOUCH_STATE_NONE: tp_edge_scroll_handle_none(tp, t, event); break; @@ -218,6 +249,12 @@ tp_edge_scroll_handle_event(struct tp_dispatch *tp, tp_edge_scroll_handle_area(tp, t, event); break; } + + log_debug(libinput, + "edge state: %s → %s → %s\n", + edge_state_to_str(current), + edge_event_to_str(event), + edge_state_to_str(t->scroll.edge_state)); } static void @@ -313,7 +350,6 @@ tp_edge_scroll_post_events(struct tp_dispatch *tp, uint64_t time) struct tp_touch *t; enum libinput_pointer_axis axis; double *delta; - double initial_dx, initial_dy, *initial_delta; struct normalized_coords normalized; const struct normalized_coords zero = { 0.0, 0.0 }; const struct discrete_coords zero_discrete = { 0.0, 0.0 }; @@ -340,20 +376,17 @@ tp_edge_scroll_post_events(struct tp_dispatch *tp, uint64_t time) case EDGE_RIGHT: axis = LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL; delta = &normalized.y; - initial_delta = &initial_dy; break; case EDGE_BOTTOM: axis = LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL; delta = &normalized.x; - initial_delta = &initial_dx; break; default: /* EDGE_RIGHT | EDGE_BOTTOM */ continue; /* Don't know direction yet, skip */ } normalized = tp_get_delta(t); - tp_filter_motion(tp, &normalized.x, &normalized.y, - NULL, NULL, time); + normalized = tp_filter_motion(tp, &normalized, time); switch (t->scroll.edge_state) { case EDGE_SCROLL_TOUCH_STATE_NONE: @@ -363,16 +396,11 @@ tp_edge_scroll_post_events(struct tp_dispatch *tp, uint64_t time) t->scroll.edge_state); break; case EDGE_SCROLL_TOUCH_STATE_EDGE_NEW: - initial_dx = t->point.x - t->scroll.initial.x; - initial_dy = t->point.y - t->scroll.initial.y; - tp_normalize_delta(tp, - initial_dx, - initial_dy, - &normalized); - if (fabs(*initial_delta) < DEFAULT_SCROLL_THRESHOLD) { - normalized.x = 0.0; - normalized.y = 0.0; - } + normalized = tp_normalize_delta(tp, + device_delta(t->point, + t->scroll.initial)); + if (fabs(*delta) < DEFAULT_SCROLL_THRESHOLD) + normalized = zero; break; case EDGE_SCROLL_TOUCH_STATE_EDGE: break; diff --git a/src/evdev-mt-touchpad-gestures.c b/src/evdev-mt-touchpad-gestures.c index f852ff5a..57c07feb 100644 --- a/src/evdev-mt-touchpad-gestures.c +++ b/src/evdev-mt-touchpad-gestures.c @@ -39,7 +39,7 @@ tp_get_touches_delta(struct tp_dispatch *tp, bool average) struct normalized_coords normalized; struct normalized_coords delta = {0.0, 0.0}; - for (i = 0; i < tp->real_touches; i++) { + for (i = 0; i < tp->num_slots; i++) { t = &tp->touches[i]; if (tp_touch_active(tp, t) && t->dirty) { @@ -93,14 +93,13 @@ tp_gesture_post_pointer_motion(struct tp_dispatch *tp, uint64_t time) /* When a clickpad is clicked, combine motion of all active touches */ if (tp->buttons.is_clickpad && tp->buttons.state) - delta = tp_get_combined_touches_delta(tp); + unaccel = tp_get_combined_touches_delta(tp); else - delta = tp_get_average_touches_delta(tp); + unaccel = tp_get_average_touches_delta(tp); - tp_filter_motion(tp, &delta.x, &delta.y, &unaccel.x, &unaccel.y, time); + delta = tp_filter_motion(tp, &unaccel, time); - if (delta.x != 0.0 || delta.y != 0.0 || - unaccel.x != 0.0 || unaccel.y != 0.0) { + if (!normalized_is_zero(delta) || !normalized_is_zero(unaccel)) { pointer_notify_motion(&tp->device->base, time, &delta, &unaccel); } @@ -111,10 +110,23 @@ tp_gesture_post_twofinger_scroll(struct tp_dispatch *tp, uint64_t time) { struct normalized_coords delta; - delta = tp_get_average_touches_delta(tp); - tp_filter_motion(tp, &delta.x, &delta.y, NULL, NULL, time); + if (tp->scroll.method != LIBINPUT_CONFIG_SCROLL_2FG) + return; - if (delta.x == 0.0 && delta.y == 0.0) + /* On some semi-mt models slot 0 is more accurate, so for semi-mt + * we only use slot 0. */ + if (tp->semi_mt) { + if (!tp->touches[0].dirty) + return; + + delta = tp_get_delta(&tp->touches[0]); + } else { + delta = tp_get_average_touches_delta(tp); + } + + delta = tp_filter_motion(tp, &delta, time); + + if (normalized_is_zero(delta)) return; tp_gesture_start(tp, time); @@ -154,6 +166,9 @@ tp_gesture_post_events(struct tp_dispatch *tp, uint64_t time) void tp_gesture_stop_twofinger_scroll(struct tp_dispatch *tp, uint64_t time) { + if (tp->scroll.method != LIBINPUT_CONFIG_SCROLL_2FG) + return; + evdev_stop_scroll(tp->device, time, LIBINPUT_POINTER_AXIS_SOURCE_FINGER); diff --git a/src/evdev-mt-touchpad-tap.c b/src/evdev-mt-touchpad-tap.c index 6bd7c582..fb8c9e43 100644 --- a/src/evdev-mt-touchpad-tap.c +++ b/src/evdev-mt-touchpad-tap.c @@ -34,9 +34,10 @@ #include "evdev-mt-touchpad.h" -#define CASE_RETURN_STRING(a) case a: return #a; +#define CASE_RETURN_STRING(a) case a: return #a #define DEFAULT_TAP_TIMEOUT_PERIOD 180 +#define DEFAULT_DRAG_TIMEOUT_PERIOD 500 #define DEFAULT_TAP_MOVE_THRESHOLD TP_MM_TO_DPI_NORMALIZED(3) enum tap_event { @@ -59,8 +60,8 @@ enum tap_event { */ static inline const char* -tap_state_to_str(enum tp_tap_state state) { - +tap_state_to_str(enum tp_tap_state state) +{ switch(state) { CASE_RETURN_STRING(TAP_STATE_IDLE); CASE_RETURN_STRING(TAP_STATE_HOLD); @@ -73,15 +74,18 @@ tap_state_to_str(enum tp_tap_state state) { CASE_RETURN_STRING(TAP_STATE_DRAGGING); CASE_RETURN_STRING(TAP_STATE_DRAGGING_WAIT); CASE_RETURN_STRING(TAP_STATE_DRAGGING_OR_DOUBLETAP); + CASE_RETURN_STRING(TAP_STATE_DRAGGING_OR_TAP); CASE_RETURN_STRING(TAP_STATE_DRAGGING_2); + CASE_RETURN_STRING(TAP_STATE_MULTITAP); + CASE_RETURN_STRING(TAP_STATE_MULTITAP_DOWN); CASE_RETURN_STRING(TAP_STATE_DEAD); } return NULL; } static inline const char* -tap_event_to_str(enum tap_event event) { - +tap_event_to_str(enum tap_event event) +{ switch(event) { CASE_RETURN_STRING(TAP_EVENT_TOUCH); CASE_RETURN_STRING(TAP_EVENT_MOTION); @@ -126,6 +130,12 @@ tp_tap_set_timer(struct tp_dispatch *tp, uint64_t time) libinput_timer_set(&tp->tap.timer, time + DEFAULT_TAP_TIMEOUT_PERIOD); } +static void +tp_tap_set_drag_timer(struct tp_dispatch *tp, uint64_t time) +{ + libinput_timer_set(&tp->tap.timer, time + DEFAULT_DRAG_TIMEOUT_PERIOD); +} + static void tp_tap_clear_timer(struct tp_dispatch *tp) { @@ -145,9 +155,10 @@ tp_tap_idle_handle_event(struct tp_dispatch *tp, tp_tap_set_timer(tp, time); break; case TAP_EVENT_RELEASE: + break; case TAP_EVENT_MOTION: log_bug_libinput(libinput, - "invalid event, no fingers are down\n"); + "invalid tap event, no fingers are down\n"); break; case TAP_EVENT_TIMEOUT: break; @@ -218,10 +229,11 @@ tp_tap_tapped_handle_event(struct tp_dispatch *tp, case TAP_EVENT_MOTION: case TAP_EVENT_RELEASE: log_bug_libinput(libinput, - "invalid event when fingers are up\n"); + "invalid tap event when fingers are up\n"); break; case TAP_EVENT_TOUCH: tp->tap.state = TAP_STATE_DRAGGING_OR_DOUBLETAP; + tp_tap_set_timer(tp, time); break; case TAP_EVENT_TIMEOUT: tp->tap.state = TAP_STATE_IDLE; @@ -351,11 +363,8 @@ tp_tap_dragging_or_doubletap_handle_event(struct tp_dispatch *tp, tp->tap.state = TAP_STATE_DRAGGING_2; break; case TAP_EVENT_RELEASE: - tp->tap.state = TAP_STATE_IDLE; + tp->tap.state = TAP_STATE_MULTITAP; tp_tap_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED); - tp_tap_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_PRESSED); - tp_tap_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED); - tp_tap_clear_timer(tp); break; case TAP_EVENT_MOTION: case TAP_EVENT_TIMEOUT: @@ -380,7 +389,7 @@ tp_tap_dragging_handle_event(struct tp_dispatch *tp, break; case TAP_EVENT_RELEASE: tp->tap.state = TAP_STATE_DRAGGING_WAIT; - tp_tap_set_timer(tp, time); + tp_tap_set_drag_timer(tp, time); break; case TAP_EVENT_MOTION: case TAP_EVENT_TIMEOUT: @@ -401,8 +410,8 @@ tp_tap_dragging_wait_handle_event(struct tp_dispatch *tp, switch (event) { case TAP_EVENT_TOUCH: - tp->tap.state = TAP_STATE_DRAGGING; - tp_tap_clear_timer(tp); + tp->tap.state = TAP_STATE_DRAGGING_OR_TAP; + tp_tap_set_timer(tp, time); break; case TAP_EVENT_RELEASE: case TAP_EVENT_MOTION: @@ -418,6 +427,32 @@ tp_tap_dragging_wait_handle_event(struct tp_dispatch *tp, } } +static void +tp_tap_dragging_tap_handle_event(struct tp_dispatch *tp, + struct tp_touch *t, + enum tap_event event, uint64_t time) +{ + + switch (event) { + case TAP_EVENT_TOUCH: + tp->tap.state = TAP_STATE_DRAGGING_2; + tp_tap_clear_timer(tp); + break; + case TAP_EVENT_RELEASE: + tp->tap.state = TAP_STATE_IDLE; + tp_tap_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED); + break; + case TAP_EVENT_MOTION: + case TAP_EVENT_TIMEOUT: + tp->tap.state = TAP_STATE_DRAGGING; + break; + case TAP_EVENT_BUTTON: + tp->tap.state = TAP_STATE_DEAD; + tp_tap_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED); + break; + } +} + static void tp_tap_dragging2_handle_event(struct tp_dispatch *tp, struct tp_touch *t, @@ -443,6 +478,78 @@ tp_tap_dragging2_handle_event(struct tp_dispatch *tp, } } +static void +tp_tap_multitap_handle_event(struct tp_dispatch *tp, + struct tp_touch *t, + enum tap_event event, uint64_t time) +{ + struct libinput *libinput = tp->device->base.seat->libinput; + + switch (event) { + case TAP_EVENT_RELEASE: + log_bug_libinput(libinput, + "invalid tap event, no fingers are down\n"); + break; + case TAP_EVENT_TOUCH: + tp->tap.state = TAP_STATE_MULTITAP_DOWN; + tp->tap.multitap_last_time = time; + tp_tap_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_PRESSED); + tp_tap_set_timer(tp, time); + break; + case TAP_EVENT_MOTION: + log_bug_libinput(libinput, + "invalid tap event, no fingers are down\n"); + break; + case TAP_EVENT_TIMEOUT: + tp->tap.state = TAP_STATE_IDLE; + tp_tap_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_PRESSED); + tp_tap_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED); + break; + case TAP_EVENT_BUTTON: + tp->tap.state = TAP_STATE_IDLE; + tp_tap_clear_timer(tp); + break; + } +} + +static void +tp_tap_multitap_down_handle_event(struct tp_dispatch *tp, + struct tp_touch *t, + enum tap_event event, + uint64_t time) +{ + switch (event) { + case TAP_EVENT_RELEASE: + tp->tap.state = TAP_STATE_MULTITAP; + tp_tap_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED); + break; + case TAP_EVENT_TOUCH: + tp->tap.state = TAP_STATE_DRAGGING_2; + tp_tap_notify(tp, + tp->tap.multitap_last_time, + 1, + LIBINPUT_BUTTON_STATE_RELEASED); + tp_tap_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_PRESSED); + tp_tap_clear_timer(tp); + break; + case TAP_EVENT_MOTION: + case TAP_EVENT_TIMEOUT: + tp->tap.state = TAP_STATE_DRAGGING; + tp_tap_notify(tp, + tp->tap.multitap_last_time, + 1, + LIBINPUT_BUTTON_STATE_RELEASED); + tp_tap_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_PRESSED); + tp_tap_clear_timer(tp); + break; + case TAP_EVENT_BUTTON: + tp->tap.state = TAP_STATE_DEAD; + tp_tap_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED); + tp_tap_clear_timer(tp); + break; + } +} + static void tp_tap_dead_handle_event(struct tp_dispatch *tp, struct tp_touch *t, @@ -508,9 +615,18 @@ tp_tap_handle_event(struct tp_dispatch *tp, case TAP_STATE_DRAGGING_WAIT: tp_tap_dragging_wait_handle_event(tp, t, event, time); break; + case TAP_STATE_DRAGGING_OR_TAP: + tp_tap_dragging_tap_handle_event(tp, t, event, time); + break; case TAP_STATE_DRAGGING_2: tp_tap_dragging2_handle_event(tp, t, event, time); break; + case TAP_STATE_MULTITAP: + tp_tap_multitap_handle_event(tp, t, event, time); + break; + case TAP_STATE_MULTITAP_DOWN: + tp_tap_multitap_down_handle_event(tp, t, event, time); + break; case TAP_STATE_DEAD: tp_tap_dead_handle_event(tp, t, event, time); break; @@ -530,16 +646,11 @@ static bool tp_tap_exceeds_motion_threshold(struct tp_dispatch *tp, struct tp_touch *t) { - int threshold = DEFAULT_TAP_MOVE_THRESHOLD; - double dx, dy; - struct normalized_coords normalized; + struct normalized_coords norm = + tp_normalize_delta(tp, device_delta(t->point, + t->tap.initial)); - dx = abs(t->tap.initial.x - t->point.x); - dy = abs(t->tap.initial.y - t->point.y); - tp_normalize_delta(tp, dx, dy, &normalized); - - return normalized.x * normalized.x + normalized.y * normalized.y - > threshold * threshold; + return normalized_length(norm) > DEFAULT_TAP_MOVE_THRESHOLD; } static bool @@ -575,6 +686,14 @@ tp_tap_handle_state(struct tp_dispatch *tp, uint64_t time) t->tap.state = TAP_TOUCH_STATE_TOUCH; t->tap.initial = t->point; tp_tap_handle_event(tp, t, TAP_EVENT_TOUCH, time); + + /* If we think this is a palm, pretend there's a + * motion event which will prevent tap clicks + * without requiring extra states in the FSM. + */ + if (tp_palm_tap_is_palm(tp, t)) + tp_tap_handle_event(tp, t, TAP_EVENT_MOTION, time); + } else if (t->state == TOUCH_END) { tp_tap_handle_event(tp, t, TAP_EVENT_RELEASE, time); t->tap.state = TAP_TOUCH_STATE_IDLE; @@ -603,8 +722,10 @@ tp_tap_handle_state(struct tp_dispatch *tp, uint64_t time) case TAP_STATE_TOUCH: case TAP_STATE_TAPPED: case TAP_STATE_DRAGGING_OR_DOUBLETAP: + case TAP_STATE_DRAGGING_OR_TAP: case TAP_STATE_TOUCH_2: case TAP_STATE_TOUCH_3: + case TAP_STATE_MULTITAP_DOWN: filter_motion = 1; break; @@ -780,6 +901,7 @@ tp_tap_dragging(struct tp_dispatch *tp) case TAP_STATE_DRAGGING: case TAP_STATE_DRAGGING_2: case TAP_STATE_DRAGGING_WAIT: + case TAP_STATE_DRAGGING_OR_TAP: return true; default: return false; diff --git a/src/evdev-mt-touchpad.c b/src/evdev-mt-touchpad.c index 6282b71e..79cc0519 100644 --- a/src/evdev-mt-touchpad.c +++ b/src/evdev-mt-touchpad.c @@ -34,6 +34,9 @@ #define DEFAULT_ACCEL_NUMERATOR 3000.0 #define DEFAULT_HYSTERESIS_MARGIN_DENOMINATOR 700.0 #define DEFAULT_TRACKPOINT_ACTIVITY_TIMEOUT 500 /* ms */ +#define DEFAULT_KEYBOARD_ACTIVITY_TIMEOUT_1 200 /* ms */ +#define DEFAULT_KEYBOARD_ACTIVITY_TIMEOUT_2 500 /* ms */ +#define FAKE_FINGER_OVERFLOW (1 << 7) static inline int tp_hysteresis(int in, int center, int margin) @@ -58,33 +61,16 @@ tp_motion_history_offset(struct tp_touch *t, int offset) return &t->history.samples[offset_index]; } -void +struct normalized_coords tp_filter_motion(struct tp_dispatch *tp, - double *dx, double *dy, - double *dx_unaccel, double *dy_unaccel, + const struct normalized_coords *unaccelerated, uint64_t time) { - struct normalized_coords unaccelerated; - struct normalized_coords accelerated; + if (normalized_is_zero(*unaccelerated)) + return *unaccelerated; - unaccelerated.x = *dx; - unaccelerated.y = *dy; - - if (unaccelerated.x != 0.0 || unaccelerated.y != 0.0) - accelerated = filter_dispatch(tp->device->pointer.filter, - &unaccelerated, - tp, - time); - else - accelerated = unaccelerated; - - if (dx_unaccel) - *dx_unaccel = unaccelerated.x; - if (dy_unaccel) - *dy_unaccel = unaccelerated.y; - - *dx = accelerated.x; - *dy = accelerated.y; + return filter_dispatch(tp->device->pointer.filter, + unaccelerated, tp, time); } static inline void @@ -144,8 +130,10 @@ tp_get_touch(struct tp_dispatch *tp, unsigned int slot) static inline unsigned int tp_fake_finger_count(struct tp_dispatch *tp) { - /* don't count BTN_TOUCH */ - return ffs(tp->fake_touches >> 1); + if (tp->fake_touches & FAKE_FINGER_OVERFLOW) + return FAKE_FINGER_OVERFLOW; + else /* don't count BTN_TOUCH */ + return ffs(tp->fake_touches >> 1); } static inline bool @@ -163,6 +151,8 @@ tp_fake_finger_set(struct tp_dispatch *tp, switch (code) { case BTN_TOUCH: + if (!is_press) + tp->fake_touches &= ~FAKE_FINGER_OVERFLOW; shift = 0; break; case BTN_TOOL_FINGER: @@ -173,14 +163,24 @@ tp_fake_finger_set(struct tp_dispatch *tp, case BTN_TOOL_QUADTAP: shift = code - BTN_TOOL_DOUBLETAP + 2; break; + /* when QUINTTAP is released we're either switching to 6 fingers + (flag stays in place until BTN_TOUCH is released) or + one of DOUBLE/TRIPLE/QUADTAP (will clear the flag on press) */ + case BTN_TOOL_QUINTTAP: + if (is_press) + tp->fake_touches |= FAKE_FINGER_OVERFLOW; + return; default: return; } - if (is_press) + if (is_press) { + tp->fake_touches &= ~FAKE_FINGER_OVERFLOW; tp->fake_touches |= 1 << shift; - else + + } else { tp->fake_touches &= ~(0x1 << shift); + } } static inline void @@ -233,7 +233,7 @@ tp_end_touch(struct tp_dispatch *tp, struct tp_touch *t, uint64_t time) } t->dirty = true; - t->palm.is_palm = false; + t->palm.state = PALM_NONE; t->state = TOUCH_END; t->pinned.is_pinned = false; t->millis = time; @@ -261,23 +261,22 @@ tp_estimate_delta(int x0, int x1, int x2, int x3) struct normalized_coords tp_get_delta(struct tp_touch *t) { - double dx, dy; /* in device coords */ - struct normalized_coords normalized = { 0.0, 0.0 }; + struct device_float_coords delta; + const struct normalized_coords zero = { 0.0, 0.0 }; if (t->history.count < TOUCHPAD_MIN_SAMPLES) - return normalized; + return zero; - dx = tp_estimate_delta(tp_motion_history_offset(t, 0)->x, - tp_motion_history_offset(t, 1)->x, - tp_motion_history_offset(t, 2)->x, - tp_motion_history_offset(t, 3)->x); - dy = tp_estimate_delta(tp_motion_history_offset(t, 0)->y, - tp_motion_history_offset(t, 1)->y, - tp_motion_history_offset(t, 2)->y, - tp_motion_history_offset(t, 3)->y); - tp_normalize_delta(t->tp, dx, dy, &normalized); + delta.x = tp_estimate_delta(tp_motion_history_offset(t, 0)->x, + tp_motion_history_offset(t, 1)->x, + tp_motion_history_offset(t, 2)->x, + tp_motion_history_offset(t, 3)->x); + delta.y = tp_estimate_delta(tp_motion_history_offset(t, 0)->y, + tp_motion_history_offset(t, 1)->y, + tp_motion_history_offset(t, 2)->y, + tp_motion_history_offset(t, 3)->y); - return normalized; + return tp_normalize_delta(t->tp, delta); } static void @@ -303,6 +302,9 @@ tp_process_absolute(struct tp_dispatch *tp, case ABS_MT_SLOT: tp->slot = e->value; break; + case ABS_MT_DISTANCE: + t->distance = e->value; + break; case ABS_MT_TRACKING_ID: if (e->value != -1) tp_new_touch(tp, t, time); @@ -335,19 +337,18 @@ tp_process_absolute_st(struct tp_dispatch *tp, } static void -tp_process_fake_touch(struct tp_dispatch *tp, - const struct input_event *e, - uint64_t time) +tp_process_fake_touches(struct tp_dispatch *tp, + uint64_t time) { struct tp_touch *t; unsigned int nfake_touches; unsigned int i, start; - tp_fake_finger_set(tp, e->code, e->value != 0); - nfake_touches = tp_fake_finger_count(tp); + if (nfake_touches == FAKE_FINGER_OVERFLOW) + return; - start = tp->has_mt ? tp->real_touches : 0; + start = tp->has_mt ? tp->num_slots : 0; for (i = start; i < tp->ntouches; i++) { t = tp_get_touch(tp, i); if (i < nfake_touches) @@ -408,7 +409,8 @@ tp_process_key(struct tp_dispatch *tp, case BTN_TOOL_DOUBLETAP: case BTN_TOOL_TRIPLETAP: case BTN_TOOL_QUADTAP: - tp_process_fake_touch(tp, e, time); + case BTN_TOOL_QUINTTAP: + tp_fake_finger_set(tp, e->code, !!e->value); break; case BTN_0: case BTN_1: @@ -455,29 +457,58 @@ int tp_touch_active(struct tp_dispatch *tp, struct tp_touch *t) { return (t->state == TOUCH_BEGIN || t->state == TOUCH_UPDATE) && - !t->palm.is_palm && + t->palm.state == PALM_NONE && !t->pinned.is_pinned && tp_button_touch_active(tp, t) && tp_edge_scroll_touch_active(tp, t); } +bool +tp_palm_tap_is_palm(struct tp_dispatch *tp, struct tp_touch *t) +{ + if (t->state != TOUCH_BEGIN) + return false; + + if (t->point.x > tp->palm.left_edge && + t->point.x < tp->palm.right_edge) + return false; + + /* We're inside the left/right palm edge and in the northern half of + * the touchpad - this tap is a palm */ + if (t->point.y < tp->palm.vert_center) + return true; + + return false; +} + static void tp_palm_detect(struct tp_dispatch *tp, struct tp_touch *t, uint64_t time) { const int PALM_TIMEOUT = 200; /* ms */ const int DIRECTIONS = NE|E|SE|SW|W|NW; + struct device_float_coords delta; + int dirs; + + if (tp->sendevents.keyboard_active && + t->state == TOUCH_BEGIN) { + t->palm.state = PALM_TYPING; + t->palm.time = time; + t->palm.first = t->point; + return; + } /* If labelled a touch as palm, we unlabel as palm when we move out of the palm edge zone within the timeout, provided the direction is within 45 degrees of the horizontal. */ - if (t->palm.is_palm) { + if (t->palm.state == PALM_EDGE) { if (time < t->palm.time + PALM_TIMEOUT && (t->point.x > tp->palm.left_edge && t->point.x < tp->palm.right_edge)) { - int dirs = vector_get_direction(t->point.x - t->palm.first.x, - t->point.y - t->palm.first.y); + delta = device_delta(t->point, t->palm.first); + dirs = normalized_get_direction( + tp_normalize_delta(tp, delta)); if ((dirs & DIRECTIONS) && !(dirs & ~DIRECTIONS)) { - t->palm.is_palm = false; + t->palm.state = PALM_NONE; } } return; @@ -496,13 +527,35 @@ tp_palm_detect(struct tp_dispatch *tp, struct tp_touch *t, uint64_t time) tp_button_is_inside_softbutton_area(tp, t)) return; - t->palm.is_palm = true; + t->palm.state = PALM_EDGE; t->palm.time = time; t->palm.first = t->point; } static void -tp_unhover_touches(struct tp_dispatch *tp, uint64_t time) +tp_unhover_abs_distance(struct tp_dispatch *tp, uint64_t time) +{ + struct tp_touch *t; + unsigned int i; + + for (i = 0; i < tp->ntouches; i++) { + t = tp_get_touch(tp, i); + + if (t->state == TOUCH_HOVERING) { + if (t->distance == 0) { + /* avoid jumps when landing a finger */ + tp_motion_history_reset(t); + tp_begin_touch(tp, t, time); + } + } else { + if (t->distance > 0) + tp_end_touch(tp, t, time); + } + } +} + +static void +tp_unhover_fake_touches(struct tp_dispatch *tp, uint64_t time) { struct tp_touch *t; unsigned int nfake_touches; @@ -512,6 +565,9 @@ tp_unhover_touches(struct tp_dispatch *tp, uint64_t time) return; nfake_touches = tp_fake_finger_count(tp); + if (nfake_touches == FAKE_FINGER_OVERFLOW) + return; + if (tp->nfingers_down == nfake_touches && ((tp->nfingers_down == 0 && !tp_fake_finger_is_touching(tp)) || (tp->nfingers_down > 0 && tp_fake_finger_is_touching(tp)))) @@ -544,7 +600,8 @@ tp_unhover_touches(struct tp_dispatch *tp, uint64_t time) for (i = tp->ntouches - 1; i >= 0; i--) { t = tp_get_touch(tp, i); - if (t->state == TOUCH_HOVERING) + if (t->state == TOUCH_HOVERING || + t->state == TOUCH_NONE) continue; tp_end_touch(tp, t, time); @@ -556,6 +613,16 @@ tp_unhover_touches(struct tp_dispatch *tp, uint64_t time) } } +static void +tp_unhover_touches(struct tp_dispatch *tp, uint64_t time) +{ + if (tp->reports_distance) + tp_unhover_abs_distance(tp, time); + else + tp_unhover_fake_touches(tp, time); + +} + static void tp_process_state(struct tp_dispatch *tp, uint64_t time) { @@ -563,6 +630,7 @@ tp_process_state(struct tp_dispatch *tp, uint64_t time) struct tp_touch *first = tp_get_touch(tp, 0); unsigned int i; + tp_process_fake_touches(tp, time); tp_unhover_touches(tp, time); for (i = 0; i < tp->ntouches; i++) { @@ -572,7 +640,7 @@ tp_process_state(struct tp_dispatch *tp, uint64_t time) if (tp->semi_mt && tp->nfingers_down != tp->old_nfingers_down) tp_motion_history_reset(t); - if (i >= tp->real_touches && t->state != TOUCH_NONE) { + if (i >= tp->num_slots && t->state != TOUCH_NONE) { t->point = first->point; if (!t->dirty) t->dirty = first->dirty; @@ -647,7 +715,9 @@ tp_post_events(struct tp_dispatch *tp, uint64_t time) filter_motion |= tp_tap_handle_state(tp, time); filter_motion |= tp_post_button_events(tp, time); - if (filter_motion || tp->sendevents.trackpoint_active) { + if (filter_motion || + tp->sendevents.trackpoint_active || + tp->sendevents.keyboard_active) { tp_edge_scroll_stop_events(tp, time); tp_gesture_stop(tp, time); return; @@ -669,10 +739,10 @@ tp_handle_state(struct tp_dispatch *tp, } static void -tp_process(struct evdev_dispatch *dispatch, - struct evdev_device *device, - struct input_event *e, - uint64_t time) +tp_interface_process(struct evdev_dispatch *dispatch, + struct evdev_device *device, + struct input_event *e, + uint64_t time) { struct tp_dispatch *tp = (struct tp_dispatch *)dispatch; @@ -697,14 +767,19 @@ static void tp_remove_sendevents(struct tp_dispatch *tp) { libinput_timer_cancel(&tp->sendevents.trackpoint_timer); + libinput_timer_cancel(&tp->sendevents.keyboard_timer); if (tp->buttons.trackpoint) libinput_device_remove_event_listener( &tp->sendevents.trackpoint_listener); + + if (tp->sendevents.keyboard) + libinput_device_remove_event_listener( + &tp->sendevents.keyboard_listener); } static void -tp_remove(struct evdev_dispatch *dispatch) +tp_interface_remove(struct evdev_dispatch *dispatch) { struct tp_dispatch *tp = (struct tp_dispatch*)dispatch; @@ -717,7 +792,7 @@ tp_remove(struct evdev_dispatch *dispatch) } static void -tp_destroy(struct evdev_dispatch *dispatch) +tp_interface_destroy(struct evdev_dispatch *dispatch) { struct tp_dispatch *tp = (struct tp_dispatch*)dispatch; @@ -726,6 +801,12 @@ tp_destroy(struct evdev_dispatch *dispatch) free(tp); } +static void +tp_release_fake_touches(struct tp_dispatch *tp) +{ + tp->fake_touches = 0; +} + static void tp_clear_state(struct tp_dispatch *tp) { @@ -749,6 +830,7 @@ tp_clear_state(struct tp_dispatch *tp) tp_for_each_touch(tp, t) { tp_end_sequence(tp, t, now); } + tp_release_fake_touches(tp); tp_handle_state(tp, now); } @@ -771,6 +853,15 @@ tp_suspend(struct tp_dispatch *tp, struct evdev_device *device) } } +static void +tp_interface_suspend(struct evdev_dispatch *dispatch, + struct evdev_device *device) +{ + struct tp_dispatch *tp = (struct tp_dispatch *)dispatch; + + tp_clear_state(tp); +} + static void tp_resume(struct tp_dispatch *tp, struct evdev_device *device) { @@ -816,13 +907,76 @@ tp_trackpoint_event(uint64_t time, struct libinput_event *event, void *data) } static void -tp_device_added(struct evdev_device *device, - struct evdev_device *added_device) +tp_keyboard_timeout(uint64_t now, void *data) +{ + struct tp_dispatch *tp = data; + + tp_tap_resume(tp, now); + tp->sendevents.keyboard_active = false; +} + +static void +tp_keyboard_event(uint64_t time, struct libinput_event *event, void *data) +{ + struct tp_dispatch *tp = data; + struct libinput_event_keyboard *kbdev; + unsigned int timeout; + + if (event->type != LIBINPUT_EVENT_KEYBOARD_KEY) + return; + + kbdev = libinput_event_get_keyboard_event(event); + + /* Only trigger the timer on key down. */ + if (libinput_event_keyboard_get_key_state(kbdev) != + LIBINPUT_KEY_STATE_PRESSED) + return; + + /* modifier keys don't trigger disable-while-typing so things like + * ctrl+zoom or ctrl+click are possible */ + switch (libinput_event_keyboard_get_key(kbdev)) { + case KEY_LEFTCTRL: + case KEY_RIGHTCTRL: + case KEY_LEFTALT: + case KEY_RIGHTALT: + case KEY_LEFTSHIFT: + case KEY_RIGHTSHIFT: + case KEY_FN: + return; + default: + break; + } + + if (!tp->sendevents.keyboard_active) { + tp_edge_scroll_stop_events(tp, time); + tp_gesture_stop(tp, time); + tp_tap_suspend(tp, time); + tp->sendevents.keyboard_active = true; + timeout = DEFAULT_KEYBOARD_ACTIVITY_TIMEOUT_1; + } else { + timeout = DEFAULT_KEYBOARD_ACTIVITY_TIMEOUT_2; + } + + libinput_timer_set(&tp->sendevents.keyboard_timer, + time + timeout); +} + +static void +tp_interface_device_added(struct evdev_device *device, + struct evdev_device *added_device) { struct tp_dispatch *tp = (struct tp_dispatch*)device->dispatch; + unsigned int bus_tp = libevdev_get_id_bustype(device->evdev), + bus_trp = libevdev_get_id_bustype(added_device->evdev), + bus_kbd = libevdev_get_id_bustype(added_device->evdev); + bool tp_is_internal, trp_is_internal, kbd_is_internal; + + tp_is_internal = bus_tp != BUS_USB && bus_tp != BUS_BLUETOOTH; + trp_is_internal = bus_trp != BUS_USB && bus_trp != BUS_BLUETOOTH; if (tp->buttons.trackpoint == NULL && - (added_device->tags & EVDEV_TAG_TRACKPOINT)) { + (added_device->tags & EVDEV_TAG_TRACKPOINT) && + tp_is_internal && trp_is_internal) { /* Don't send any pending releases to the new trackpoint */ tp->buttons.active_is_topbutton = false; tp->buttons.trackpoint = added_device; @@ -831,6 +985,18 @@ tp_device_added(struct evdev_device *device, tp_trackpoint_event, tp); } + /* FIXME: detect external keyboard better */ + kbd_is_internal = bus_tp != BUS_BLUETOOTH && + bus_kbd == bus_tp; + if (tp_is_internal && kbd_is_internal && + tp->sendevents.keyboard == NULL) { + libinput_device_add_event_listener(&added_device->base, + &tp->sendevents.keyboard_listener, + tp_keyboard_event, tp); + tp->sendevents.keyboard = added_device; + tp->sendevents.keyboard_active = false; + } + if (tp->sendevents.current_mode != LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE) return; @@ -840,8 +1006,8 @@ tp_device_added(struct evdev_device *device, } static void -tp_device_removed(struct evdev_device *device, - struct evdev_device *removed_device) +tp_interface_device_removed(struct evdev_device *device, + struct evdev_device *removed_device) { struct tp_dispatch *tp = (struct tp_dispatch*)device->dispatch; struct libinput_device *dev; @@ -857,6 +1023,12 @@ tp_device_removed(struct evdev_device *device, tp->buttons.trackpoint = NULL; } + if (removed_device == tp->sendevents.keyboard) { + libinput_device_remove_event_listener( + &tp->sendevents.keyboard_listener); + tp->sendevents.keyboard = NULL; + } + if (tp->sendevents.current_mode != LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE) return; @@ -873,8 +1045,8 @@ tp_device_removed(struct evdev_device *device, } static void -tp_tag_device(struct evdev_device *device, - struct udev_device *udev_device) +tp_interface_tag_device(struct evdev_device *device, + struct udev_device *udev_device) { int bustype; @@ -896,15 +1068,16 @@ tp_tag_device(struct evdev_device *device, } static struct evdev_dispatch_interface tp_interface = { - tp_process, - tp_remove, - tp_destroy, - tp_device_added, - tp_device_removed, - tp_device_removed, /* device_suspended, treat as remove */ - tp_device_added, /* device_resumed, treat as add */ - tp_tag_device, - NULL, /* post_added */ + tp_interface_process, + tp_interface_suspend, + tp_interface_remove, + tp_interface_destroy, + tp_interface_device_added, + tp_interface_device_removed, + tp_interface_device_removed, /* device_suspended, treat as remove */ + tp_interface_device_added, /* device_resumed, treat as add */ + tp_interface_tag_device, + NULL, /* post_added */ }; static void @@ -915,6 +1088,28 @@ tp_init_touch(struct tp_dispatch *tp, t->has_ended = true; } +static void +tp_sync_touch(struct tp_dispatch *tp, + struct evdev_device *device, + struct tp_touch *t, + int slot) +{ + struct libevdev *evdev = device->evdev; + + if (!libevdev_fetch_slot_value(evdev, + slot, + ABS_MT_POSITION_X, + &t->point.x)) + t->point.x = libevdev_get_event_value(evdev, EV_ABS, ABS_X); + if (!libevdev_fetch_slot_value(evdev, + slot, + ABS_MT_POSITION_Y, + &t->point.y)) + t->point.y = libevdev_get_event_value(evdev, EV_ABS, ABS_Y); + + libevdev_fetch_slot_value(evdev, slot, ABS_MT_DISTANCE, &t->distance); +} + static int tp_init_slots(struct tp_dispatch *tp, struct evdev_device *device) @@ -934,11 +1129,11 @@ tp_init_slots(struct tp_dispatch *tp, absinfo = libevdev_get_abs_info(device->evdev, ABS_MT_SLOT); if (absinfo) { - tp->real_touches = absinfo->maximum + 1; + tp->num_slots = absinfo->maximum + 1; tp->slot = absinfo->value; tp->has_mt = true; } else { - tp->real_touches = 1; + tp->num_slots = 1; tp->slot = 0; tp->has_mt = false; } @@ -954,7 +1149,7 @@ tp_init_slots(struct tp_dispatch *tp, } } - tp->ntouches = max(tp->real_touches, n_btn_tool_touches); + tp->ntouches = max(tp->num_slots, n_btn_tool_touches); tp->touches = calloc(tp->ntouches, sizeof(struct tp_touch)); if (!tp->touches) return -1; @@ -962,6 +1157,12 @@ tp_init_slots(struct tp_dispatch *tp, for (i = 0; i < tp->ntouches; i++) tp_init_touch(tp, &tp->touches[i]); + /* Always sync the first touch so we get ABS_X/Y synced on + * single-touch touchpads */ + tp_sync_touch(tp, device, &tp->touches[0], 0); + for (i = 1; i < tp->num_slots; i++) + tp_sync_touch(tp, device, &tp->touches[i], i); + return 0; } @@ -969,6 +1170,7 @@ static int tp_init_accel(struct tp_dispatch *tp, double diagonal) { int res_x, res_y; + accel_profile_func_t profile; res_x = tp->device->abs.absinfo_x->resolution; res_y = tp->device->abs.absinfo_y->resolution; @@ -992,9 +1194,16 @@ tp_init_accel(struct tp_dispatch *tp, double diagonal) tp->accel.y_scale_coeff = DEFAULT_ACCEL_NUMERATOR / diagonal; } - if (evdev_device_init_pointer_acceleration( - tp->device, - touchpad_accel_profile_linear) == -1) + switch (tp->device->model) { + case EVDEV_MODEL_LENOVO_X230: + profile = touchpad_lenovo_x230_accel_profile; + break; + default: + profile = touchpad_accel_profile_linear; + break; + } + + if (evdev_device_init_pointer_acceleration(tp->device, profile) == -1) return -1; return 0; @@ -1087,13 +1296,16 @@ static int tp_init_palmdetect(struct tp_dispatch *tp, struct evdev_device *device) { - int width; + int width, height; tp->palm.right_edge = INT_MAX; tp->palm.left_edge = INT_MIN; + tp->palm.vert_center = INT_MIN; width = abs(device->abs.absinfo_x->maximum - device->abs.absinfo_x->minimum); + height = abs(device->abs.absinfo_y->maximum - + device->abs.absinfo_y->minimum); /* Apple touchpads are always big enough to warrant palm detection */ if (evdev_device_get_id_vendor(device) != VENDOR_ID_APPLE) { @@ -1101,15 +1313,16 @@ tp_init_palmdetect(struct tp_dispatch *tp, if (device->abs.absinfo_x->resolution == 1) return 0; - /* Enable palm detection on touchpads >= 80 mm. Anything smaller + /* Enable palm detection on touchpads >= 70 mm. Anything smaller probably won't need it, until we find out it does */ - if (width/device->abs.absinfo_x->resolution < 80) + if (width/device->abs.absinfo_x->resolution < 70) return 0; } /* palm edges are 5% of the width on each side */ tp->palm.right_edge = device->abs.absinfo_x->maximum - width * 0.05; tp->palm.left_edge = device->abs.absinfo_x->minimum + width * 0.05; + tp->palm.vert_center = device->abs.absinfo_y->minimum + height/2; return 0; } @@ -1121,6 +1334,10 @@ tp_init_sendevents(struct tp_dispatch *tp, libinput_timer_init(&tp->sendevents.trackpoint_timer, tp->device->base.seat->libinput, tp_trackpoint_timeout, tp); + + libinput_timer_init(&tp->sendevents.keyboard_timer, + tp->device->base.seat->libinput, + tp_keyboard_timeout, tp); return 0; } @@ -1143,6 +1360,10 @@ tp_init(struct tp_dispatch *tp, device->abs.absinfo_y->minimum); diagonal = sqrt(width*width + height*height); + tp->reports_distance = libevdev_has_event_code(device->evdev, + EV_ABS, + ABS_MT_DISTANCE); + tp->hysteresis_margin.x = diagonal / DEFAULT_HYSTERESIS_MARGIN_DENOMINATOR; tp->hysteresis_margin.y = @@ -1313,7 +1534,7 @@ evdev_mt_touchpad_create(struct evdev_device *device) tp->model = tp_get_model(device); if (tp_init(tp, device) != 0) { - tp_destroy(&tp->base); + tp_interface_destroy(&tp->base); return NULL; } diff --git a/src/evdev-mt-touchpad.h b/src/evdev-mt-touchpad.h index 9980f900..3d51a398 100644 --- a/src/evdev-mt-touchpad.h +++ b/src/evdev-mt-touchpad.h @@ -61,6 +61,12 @@ enum touch_state { TOUCH_END }; +enum touch_palm_state { + PALM_NONE = 0, + PALM_EDGE, + PALM_TYPING, +}; + enum button_event { BUTTON_EVENT_IN_BOTTOM_R = 30, BUTTON_EVENT_IN_BOTTOM_L, @@ -94,9 +100,12 @@ enum tp_tap_state { TAP_STATE_TOUCH_3, TAP_STATE_TOUCH_3_HOLD, TAP_STATE_DRAGGING_OR_DOUBLETAP, + TAP_STATE_DRAGGING_OR_TAP, TAP_STATE_DRAGGING, TAP_STATE_DRAGGING_WAIT, TAP_STATE_DRAGGING_2, + TAP_STATE_MULTITAP, + TAP_STATE_MULTITAP_DOWN, TAP_STATE_DEAD, /**< finger count exceeded */ }; @@ -127,6 +136,7 @@ struct tp_touch { bool dirty; struct device_coords point; uint64_t millis; + int distance; /* distance == 0 means touch */ struct { struct device_coords samples[TOUCHPAD_HISTORY_LENGTH]; @@ -167,7 +177,7 @@ struct tp_touch { } scroll; struct { - bool is_palm; + enum touch_palm_state state; struct device_coords first; /* first coordinates if is_palm == true */ uint32_t time; /* first timestamp if is_palm == true */ } palm; @@ -181,9 +191,10 @@ struct tp_dispatch { unsigned int slot; /* current slot */ bool has_mt; bool semi_mt; + bool reports_distance; /* does the device support true hovering */ enum touchpad_model model; - unsigned int real_touches; /* number of slots */ + unsigned int num_slots; /* number of slots */ unsigned int ntouches; /* no slots inc. fakes */ struct tp_touch *touches; /* len == ntouches */ /* bit 0: BTN_TOUCH @@ -255,41 +266,50 @@ struct tp_dispatch { struct libinput_timer timer; enum tp_tap_state state; uint32_t buttons_pressed; + uint64_t multitap_last_time; } tap; struct { int32_t right_edge; /* in device coordinates */ int32_t left_edge; /* in device coordinates */ + int32_t vert_center; /* in device coordinates */ } palm; struct { struct libinput_device_config_send_events config; enum libinput_config_send_events_mode current_mode; + bool trackpoint_active; struct libinput_event_listener trackpoint_listener; struct libinput_timer trackpoint_timer; + + bool keyboard_active; + struct libinput_event_listener keyboard_listener; + struct libinput_timer keyboard_timer; + struct evdev_device *keyboard; } sendevents; }; #define tp_for_each_touch(_tp, _t) \ for (unsigned int _i = 0; _i < (_tp)->ntouches && (_t = &(_tp)->touches[_i]); _i++) -static inline void -tp_normalize_delta(struct tp_dispatch *tp, - double dx, double dy, - struct normalized_coords *normalized) +static inline struct normalized_coords +tp_normalize_delta(struct tp_dispatch *tp, struct device_float_coords delta) { - normalized->x = dx * tp->accel.x_scale_coeff; - normalized->y = dy * tp->accel.y_scale_coeff; + struct normalized_coords normalized; + + normalized.x = delta.x * tp->accel.x_scale_coeff; + normalized.y = delta.y * tp->accel.y_scale_coeff; + + return normalized; } struct normalized_coords tp_get_delta(struct tp_touch *t); -void +struct normalized_coords tp_filter_motion(struct tp_dispatch *tp, - double *dx, double *dy, - double *dx_unaccel, double *dy_unaccel, + const struct normalized_coords *unaccelerated, uint64_t time); int @@ -385,4 +405,7 @@ tp_gesture_post_events(struct tp_dispatch *tp, uint64_t time); void tp_gesture_stop_twofinger_scroll(struct tp_dispatch *tp, uint64_t time); +bool +tp_palm_tap_is_palm(struct tp_dispatch *tp, struct tp_touch *t); + #endif diff --git a/src/evdev-tablet.c b/src/evdev-tablet.c index 3e3924ca..301fe246 100644 --- a/src/evdev-tablet.c +++ b/src/evdev-tablet.c @@ -1022,6 +1022,7 @@ tablet_check_initial_proximity(struct evdev_device *device, static struct evdev_dispatch_interface tablet_interface = { tablet_process, + NULL, /* suspend */ NULL, /* remove */ tablet_destroy, NULL, /* device_added */ diff --git a/src/evdev.c b/src/evdev.c index 6a3526a8..68d83a9e 100644 --- a/src/evdev.c +++ b/src/evdev.c @@ -137,6 +137,21 @@ evdev_keyboard_notify_key(struct evdev_device *device, keyboard_notify_key(&device->base, time, key, state); } +void +evdev_pointer_notify_physical_button(struct evdev_device *device, + uint32_t time, + int button, + enum libinput_button_state state) +{ + if (evdev_middlebutton_filter_button(device, + time, + button, + state)) + return; + + evdev_pointer_notify_button(device, time, button, state); +} + void evdev_pointer_notify_button(struct evdev_device *device, uint32_t time, @@ -151,13 +166,13 @@ evdev_pointer_notify_button(struct evdev_device *device, (state == LIBINPUT_BUTTON_STATE_RELEASED && down_count == 0)) { pointer_notify_button(&device->base, time, button, state); - if (state == LIBINPUT_BUTTON_STATE_RELEASED && - device->left_handed.change_to_enabled) - device->left_handed.change_to_enabled(device); + if (state == LIBINPUT_BUTTON_STATE_RELEASED) { + if (device->left_handed.change_to_enabled) + device->left_handed.change_to_enabled(device); - if (state == LIBINPUT_BUTTON_STATE_RELEASED && - device->scroll.change_scroll_method) - device->scroll.change_scroll_method(device); + if (device->scroll.change_scroll_method) + device->scroll.change_scroll_method(device); + } } } @@ -230,8 +245,8 @@ normalize_delta(struct evdev_device *device, const struct device_coords *delta, struct normalized_coords *normalized) { - normalized->x = delta->x * (double)device->dpi / DEFAULT_MOUSE_DPI; - normalized->y = delta->y * (double)device->dpi / DEFAULT_MOUSE_DPI; + normalized->x = delta->x * DEFAULT_MOUSE_DPI / (double)device->dpi; + normalized->y = delta->y * DEFAULT_MOUSE_DPI / (double)device->dpi; } static void @@ -266,12 +281,13 @@ evdev_flush_pending_event(struct evdev_device *device, uint64_t time) } /* Apply pointer acceleration. */ - accel = filter_dispatch(device->pointer.filter, &unaccel, device, time); + accel = filter_dispatch(device->pointer.filter, + &unaccel, + device, + time); - if (accel.x == 0.0 && accel.y == 0.0 && - unaccel.x == 0.0 && unaccel.y == 0.0) { + if (normalized_is_zero(accel) && normalized_is_zero(unaccel)) break; - } pointer_notify_motion(base, time, &accel, &unaccel); break; @@ -432,10 +448,10 @@ evdev_button_scroll_button(struct evdev_device *device, } else { /* If the button is released quickly enough emit the * button press/release events. */ - evdev_pointer_notify_button(device, time, + evdev_pointer_notify_physical_button(device, time, device->scroll.button, LIBINPUT_BUTTON_STATE_PRESSED); - evdev_pointer_notify_button(device, time, + evdev_pointer_notify_physical_button(device, time, device->scroll.button, LIBINPUT_BUTTON_STATE_RELEASED); } @@ -507,7 +523,7 @@ evdev_process_key(struct evdev_device *device, evdev_button_scroll_button(device, time, e->value); break; } - evdev_pointer_notify_button( + evdev_pointer_notify_physical_button( device, time, evdev_to_left_handed(device, e->code), @@ -744,6 +760,65 @@ fallback_process(struct evdev_dispatch *dispatch, } } +static void +release_pressed_keys(struct evdev_device *device) +{ + struct libinput *libinput = device->base.seat->libinput; + uint64_t time; + int code; + + if ((time = libinput_now(libinput)) == 0) + return; + + for (code = 0; code < KEY_CNT; code++) { + int count = get_key_down_count(device, code); + + if (count == 0) + continue; + + if (count > 1) { + log_bug_libinput(libinput, + "Key %d is down %d times.\n", + code, + count); + } + + switch (get_key_type(code)) { + case EVDEV_KEY_TYPE_NONE: + break; + case EVDEV_KEY_TYPE_KEY: + evdev_keyboard_notify_key( + device, + time, + code, + LIBINPUT_KEY_STATE_RELEASED); + break; + case EVDEV_KEY_TYPE_BUTTON: + evdev_pointer_notify_physical_button( + device, + time, + evdev_to_left_handed(device, code), + LIBINPUT_BUTTON_STATE_RELEASED); + break; + } + + count = get_key_down_count(device, code); + if (count != 0) { + log_bug_libinput(libinput, + "Releasing key %d failed.\n", + code); + break; + } + } +} + +static void +fallback_suspend(struct evdev_dispatch *dispatch, + struct evdev_device *device) +{ + release_pressed_keys(device); +} + static void fallback_destroy(struct evdev_dispatch *dispatch) { @@ -801,6 +876,7 @@ evdev_calibration_get_default_matrix(struct libinput_device *libinput_device, struct evdev_dispatch_interface fallback_interface = { fallback_process, + fallback_suspend, NULL, /* remove */ fallback_destroy, NULL, /* device_added */ @@ -971,8 +1047,15 @@ evdev_scroll_get_default_method(struct libinput_device *device) if (libevdev_has_property(evdev->evdev, INPUT_PROP_POINTING_STICK)) return LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN; - else - return LIBINPUT_CONFIG_SCROLL_NO_SCROLL; + + /* Mice without a scroll wheel but with middle button have on-button + * scrolling by default */ + if (!libevdev_has_event_code(evdev->evdev, EV_REL, REL_WHEEL) && + !libevdev_has_event_code(evdev->evdev, EV_REL, REL_HWHEEL) && + libevdev_has_event_code(evdev->evdev, EV_KEY, BTN_MIDDLE)) + return LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN; + + return LIBINPUT_CONFIG_SCROLL_NO_SCROLL; } static enum libinput_config_status @@ -1004,8 +1087,15 @@ evdev_scroll_get_default_button(struct libinput_device *device) if (libevdev_has_property(evdev->evdev, INPUT_PROP_POINTING_STICK)) return BTN_MIDDLE; - else - return 0; + + /* A device that defaults to button scrolling defaults + to BTN_MIDDLE */ + if (evdev_scroll_get_default_method(device) == + LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN && + libevdev_has_event_code(evdev->evdev, EV_KEY, BTN_MIDDLE)) + return BTN_MIDDLE; + + return 0; } static int @@ -1131,6 +1221,24 @@ fallback_dispatch_create(struct libinput_device *device) evdev_init_calibration(evdev_device, dispatch); evdev_init_sendevents(evdev_device, dispatch); + /* BTN_MIDDLE is set on mice even when it's not present. So + * we can only use the absense of BTN_MIDDLE to mean something, i.e. + * we enable it by default on anything that only has L&R. + * If we have L&R and no middle, we don't expose it as config + * option */ + if (libevdev_has_event_code(evdev_device->evdev, EV_KEY, BTN_LEFT) && + libevdev_has_event_code(evdev_device->evdev, EV_KEY, BTN_RIGHT)) { + bool has_middle = libevdev_has_event_code(evdev_device->evdev, + EV_KEY, + BTN_MIDDLE); + bool want_config = has_middle; + bool enable_by_default = !has_middle; + + evdev_init_middlebutton(evdev_device, + enable_by_default, + want_config); + } + return dispatch; } @@ -1140,6 +1248,19 @@ evdev_process_event(struct evdev_device *device, struct input_event *e) struct evdev_dispatch *dispatch = device->dispatch; uint64_t time = e->time.tv_sec * 1000ULL + e->time.tv_usec / 1000; +#if 0 + if (libevdev_event_is_code(e, EV_SYN, SYN_REPORT)) + log_debug(device->base.seat->libinput, + "-------------- EV_SYN ------------\n"); + else + log_debug(device->base.seat->libinput, + "%-7s %-16s %-20s %4d\n", + evdev_device_get_sysname(device), + libevdev_event_type_get_name(e->type), + libevdev_event_code_get_name(e->type, e->code), + e->value); +#endif + dispatch->interface->process(dispatch, device, e, time); } @@ -1322,6 +1443,31 @@ evdev_read_wheel_click_prop(struct evdev_device *device) return angle; } + +static inline int +evdev_get_trackpoint_dpi(struct evdev_device *device) +{ + struct libinput *libinput = device->base.seat->libinput; + const char *trackpoint_accel; + double accel = DEFAULT_TRACKPOINT_ACCEL; + + trackpoint_accel = udev_device_get_property_value( + device->udev_device, "POINTINGSTICK_CONST_ACCEL"); + if (trackpoint_accel) { + accel = parse_trackpoint_accel_property(trackpoint_accel); + if (accel == 0.0) { + log_error(libinput, "Trackpoint accel property for " + "'%s' is present but invalid, " + "using %.2f instead\n", + device->devname, + DEFAULT_TRACKPOINT_ACCEL); + accel = DEFAULT_TRACKPOINT_ACCEL; + } + } + + return DEFAULT_MOUSE_DPI / accel; +} + static inline int evdev_read_dpi_prop(struct evdev_device *device) { @@ -1329,6 +1475,14 @@ evdev_read_dpi_prop(struct evdev_device *device) const char *mouse_dpi; int dpi = DEFAULT_MOUSE_DPI; + /* + * Trackpoints do not have dpi, instead hwdb may contain a + * POINTINGSTICK_CONST_ACCEL value to compensate for sensitivity + * differences between models, we translate this to a fake dpi. + */ + if (libevdev_has_property(device->evdev, INPUT_PROP_POINTING_STICK)) + return evdev_get_trackpoint_dpi(device); + mouse_dpi = udev_device_get_property_value(device->udev_device, "MOUSE_DPI"); if (mouse_dpi) { @@ -1346,6 +1500,33 @@ evdev_read_dpi_prop(struct evdev_device *device) return dpi; } +static inline enum evdev_device_model +evdev_read_model(struct evdev_device *device) +{ + const struct model_map { + const char *property; + enum evdev_device_model model; + } model_map[] = { + { "LIBINPUT_MODEL_LENOVO_X230", EVDEV_MODEL_LENOVO_X230 }, + { "LIBINPUT_MODEL_CHROMEBOOK", EVDEV_MODEL_CHROMEBOOK }, + { "LIBINPUT_MODEL_SYSTEM76_BONOBO", EVDEV_MODEL_SYSTEM76_BONOBO }, + { "LIBINPUT_MODEL_SYSTEM76_GALAGO", EVDEV_MODEL_SYSTEM76_GALAGO }, + { "LIBINPUT_MODEL_SYSTEM76_KUDU", EVDEV_MODEL_SYSTEM76_KUDU }, + { "LIBINPUT_MODEL_CLEVO_W740SU", EVDEV_MODEL_CLEVO_W740SU }, + { NULL, EVDEV_MODEL_DEFAULT }, + }; + const struct model_map *m = model_map; + + while (m->property) { + if (!!udev_device_get_property_value(device->udev_device, + m->property)) + break; + m++; + } + + return m->model; +} + /* Return 1 if the given resolutions have been set, or 0 otherwise */ inline int evdev_fix_abs_resolution(struct evdev_device *device, @@ -1380,13 +1561,6 @@ evdev_fix_abs_resolution(struct evdev_device *device, absx = libevdev_get_abs_info(evdev, xcode); absy = libevdev_get_abs_info(evdev, ycode); - if ((absx->resolution == 0 && absy->resolution != 0) || - (absx->resolution != 0 && absy->resolution == 0)) { - log_bug_kernel(libinput, - "Kernel has only x or y resolution, not both.\n"); - return 0; - } - if (absx->resolution == 0 || absx->resolution == EVDEV_FAKE_RESOLUTION) { fixed = *absx; fixed.resolution = xresolution; @@ -1434,6 +1608,18 @@ evdev_device_get_udev_tags(struct evdev_device *device, return tags; } +/* Fake MT devices have the ABS_MT_SLOT bit set because of + the limited ABS_* range - they aren't MT devices, they + just have too many ABS_ axes */ +static inline bool +evdev_is_fake_mt_device(struct evdev_device *device) +{ + struct libevdev *evdev = device->evdev; + + return libevdev_has_event_code(evdev, EV_ABS, ABS_MT_SLOT) && + libevdev_get_num_slots(evdev) == -1; +} + static inline void evdev_fix_android_mt(struct evdev_device *device) { @@ -1444,12 +1630,13 @@ evdev_fix_android_mt(struct evdev_device *device) return; if (!libevdev_has_event_code(evdev, EV_ABS, ABS_MT_POSITION_X) || - !libevdev_has_event_code(evdev, EV_ABS, ABS_MT_POSITION_Y)) + !libevdev_has_event_code(evdev, EV_ABS, ABS_MT_POSITION_Y) || + evdev_is_fake_mt_device(device)) return; - libevdev_set_abs_info(evdev, ABS_X, + libevdev_enable_event_code(evdev, EV_ABS, ABS_X, libevdev_get_abs_info(evdev, ABS_MT_POSITION_X)); - libevdev_set_abs_info(evdev, ABS_Y, + libevdev_enable_event_code(evdev, EV_ABS, ABS_Y, libevdev_get_abs_info(evdev, ABS_MT_POSITION_Y)); } @@ -1464,11 +1651,27 @@ evdev_check_min_max(struct evdev_device *device, unsigned int code) absinfo = libevdev_get_abs_info(evdev, code); if (absinfo->minimum == absinfo->maximum) { - log_bug_kernel(device->base.seat->libinput, - "Device '%s' has min == max on %s\n", - device->devname, - libevdev_event_code_get_name(EV_ABS, code)); - return -1; + /* Some devices have a sort-of legitimate min/max of 0 for + * ABS_MISC and above (e.g. Roccat Kone XTD). Don't ignore + * them, simply disable the axes so we won't get events, + * we don't know what to do with them anyway. + */ + if (absinfo->minimum == 0 && + code >= ABS_MISC && code < ABS_MT_SLOT) { + log_info(device->base.seat->libinput, + "Disabling EV_ABS %#x on device '%s' (min == max == 0)\n", + code, + device->devname); + libevdev_disable_event_code(device->evdev, + EV_ABS, + code); + } else { + log_bug_kernel(device->base.seat->libinput, + "Device '%s' has min == max on %s\n", + device->devname, + libevdev_event_code_get_name(EV_ABS, code)); + return -1; + } } return 0; @@ -1477,17 +1680,46 @@ evdev_check_min_max(struct evdev_device *device, unsigned int code) static int evdev_reject_device(struct evdev_device *device) { + struct libinput *libinput = device->base.seat->libinput; struct libevdev *evdev = device->evdev; unsigned int code; + const struct input_absinfo *absx, *absy; if (libevdev_has_event_code(evdev, EV_ABS, ABS_X) ^ libevdev_has_event_code(evdev, EV_ABS, ABS_Y)) return -1; + if (libevdev_has_event_code(evdev, EV_REL, REL_X) ^ + libevdev_has_event_code(evdev, EV_REL, REL_Y)) + return -1; + if (libevdev_has_event_code(evdev, EV_ABS, ABS_MT_POSITION_X) ^ libevdev_has_event_code(evdev, EV_ABS, ABS_MT_POSITION_Y)) return -1; + if (libevdev_has_event_code(evdev, EV_ABS, ABS_X)) { + absx = libevdev_get_abs_info(evdev, ABS_X); + absy = libevdev_get_abs_info(evdev, ABS_Y); + if ((absx->resolution == 0 && absy->resolution != 0) || + (absx->resolution != 0 && absy->resolution == 0)) { + log_bug_kernel(libinput, + "Kernel has only x or y resolution, not both.\n"); + return -1; + } + } + + if (!evdev_is_fake_mt_device(device) && + libevdev_has_event_code(evdev, EV_ABS, ABS_MT_POSITION_X)) { + absx = libevdev_get_abs_info(evdev, ABS_MT_POSITION_X); + absy = libevdev_get_abs_info(evdev, ABS_MT_POSITION_Y); + if ((absx->resolution == 0 && absy->resolution != 0) || + (absx->resolution != 0 && absy->resolution == 0)) { + log_bug_kernel(libinput, + "Kernel has only x or y MT resolution, not both.\n"); + return -1; + } + } + for (code = 0; code < ABS_CNT; code++) { switch (code) { case ABS_MISC: @@ -1550,8 +1782,16 @@ evdev_configure_mt_device(struct evdev_device *device) for (slot = 0; slot < num_slots; ++slot) { slots[slot].seat_slot = -1; - slots[slot].point.x = 0; - slots[slot].point.y = 0; + + if (evdev_need_mtdev(device)) + continue; + + slots[slot].point.x = libevdev_get_slot_value(evdev, + slot, + ABS_MT_POSITION_X); + slots[slot].point.y = libevdev_get_slot_value(evdev, + slot, + ABS_MT_POSITION_Y); } device->mt.slots = slots; device->mt.slots_len = num_slots; @@ -1615,10 +1855,10 @@ evdev_configure_device(struct evdev_device *device) return -1; } - if (libevdev_has_event_code(evdev, EV_ABS, ABS_X) || - libevdev_has_event_code(evdev, EV_ABS, ABS_MT_POSITION_X)) { + if (!evdev_is_fake_mt_device(device)) evdev_fix_android_mt(device); + if (libevdev_has_event_code(evdev, EV_ABS, ABS_X)) { if (evdev_fix_abs_resolution(device, ABS_X, ABS_Y, @@ -1627,12 +1867,10 @@ evdev_configure_device(struct evdev_device *device) device->abs.fake_resolution = 1; device->abs.absinfo_x = libevdev_get_abs_info(evdev, ABS_X); device->abs.absinfo_y = libevdev_get_abs_info(evdev, ABS_Y); + device->abs.point.x = device->abs.absinfo_x->value; + device->abs.point.y = device->abs.absinfo_y->value; - /* Fake MT devices have the ABS_MT_SLOT bit set because of - the limited ABS_* range - they aren't MT devices, they - just have too many ABS_ axes */ - if (libevdev_has_event_code(evdev, EV_ABS, ABS_MT_SLOT) && - libevdev_get_num_slots(evdev) == -1) { + if (evdev_is_fake_mt_device(device)) { udev_tags &= ~EVDEV_UDEV_TAG_TOUCHSCREEN; } else if (evdev_configure_mt_device(device) == -1) { return -1; @@ -1663,8 +1901,8 @@ evdev_configure_device(struct evdev_device *device) } if (udev_tags & EVDEV_UDEV_TAG_MOUSE) { - if (!libevdev_has_event_code(evdev, EV_ABS, ABS_X) && - !libevdev_has_event_code(evdev, EV_ABS, ABS_Y) && + if (libevdev_has_event_code(evdev, EV_REL, REL_X) && + libevdev_has_event_code(evdev, EV_REL, REL_Y) && evdev_device_init_pointer_acceleration( device, pointer_accel_profile_linear) == -1) @@ -1689,6 +1927,13 @@ evdev_configure_device(struct evdev_device *device) log_info(libinput, "input device '%s', %s is a keyboard\n", device->devname, devnode); + + /* want natural-scroll config option */ + if (libevdev_has_event_code(evdev, EV_REL, REL_WHEEL) || + libevdev_has_event_code(evdev, EV_REL, REL_HWHEEL)) { + device->scroll.natural_scrolling_enabled = true; + device->seat_caps |= EVDEV_DEVICE_POINTER; + } } if (udev_tags & EVDEV_UDEV_TAG_TOUCHSCREEN) { @@ -1804,7 +2049,8 @@ evdev_device_create(struct libinput_seat *seat, /* Use non-blocking mode so that we can loop on read on * evdev_device_data() until all events on the fd are * read. mtdev_get() also expects this. */ - fd = open_restricted(libinput, devnode, O_RDWR | O_NONBLOCK); + fd = open_restricted(libinput, devnode, + O_RDWR | O_NONBLOCK | O_CLOEXEC); if (fd < 0) { log_info(libinput, "opening input device '%s' failed (%s).\n", @@ -1844,6 +2090,7 @@ evdev_device_create(struct libinput_seat *seat, device->scroll.wheel_click_angle = evdev_read_wheel_click_prop(device); device->dpi = evdev_read_dpi_prop(device); + device->model = evdev_read_model(device); /* at most 5 SYN_DROPPED log-messages per 30s */ ratelimit_init(&device->syn_drop_limit, 30ULL * 1000, 5); @@ -2044,6 +2291,15 @@ evdev_device_has_button(struct evdev_device *device, uint32_t code) return libevdev_has_event_code(device->evdev, EV_KEY, code); } +int +evdev_device_has_key(struct evdev_device *device, uint32_t code) +{ + if (!(device->seat_caps & EVDEV_DEVICE_KEYBOARD)) + return -1; + + return libevdev_has_event_code(device->evdev, EV_KEY, code); +} + static inline bool evdev_is_scrolling(const struct evdev_device *device, enum libinput_pointer_axis axis) @@ -2121,7 +2377,7 @@ evdev_post_scroll(struct evdev_device *device, LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL)) event.x = 0.0; - if (event.x != 0.0 || event.y != 0.0) { + if (!normalized_is_zero(event)) { const struct discrete_coords zero_discrete = { 0.0, 0.0 }; evdev_notify_axis(device, time, @@ -2154,49 +2410,6 @@ evdev_stop_scroll(struct evdev_device *device, device->scroll.direction = 0; } -static void -release_pressed_keys(struct evdev_device *device) -{ - struct libinput *libinput = device->base.seat->libinput; - uint64_t time; - int code; - - if ((time = libinput_now(libinput)) == 0) - return; - - for (code = 0; code < KEY_CNT; code++) { - int count = get_key_down_count(device, code); - - if (count > 1) { - log_bug_libinput(libinput, - "Key %d is down %d times.\n", - code, - count); - } - - while (get_key_down_count(device, code) > 0) { - switch (get_key_type(code)) { - case EVDEV_KEY_TYPE_NONE: - break; - case EVDEV_KEY_TYPE_KEY: - evdev_keyboard_notify_key( - device, - time, - code, - LIBINPUT_KEY_STATE_RELEASED); - break; - case EVDEV_KEY_TYPE_BUTTON: - evdev_pointer_notify_button( - device, - time, - evdev_to_left_handed(device, code), - LIBINPUT_BUTTON_STATE_RELEASED); - break; - } - } - } -} - void evdev_notify_suspended_device(struct evdev_device *device) { @@ -2242,14 +2455,16 @@ evdev_device_suspend(struct evdev_device *device) { evdev_notify_suspended_device(device); + if (device->dispatch->interface->suspend) + device->dispatch->interface->suspend(device->dispatch, + device); + if (device->source) { libinput_remove_source(device->base.seat->libinput, device->source); device->source = NULL; } - release_pressed_keys(device); - if (device->mtdev) { mtdev_close_delete(device->mtdev); device->mtdev = NULL; @@ -2279,7 +2494,8 @@ evdev_device_resume(struct evdev_device *device) return -ENODEV; devnode = udev_device_get_devnode(device->udev_device); - fd = open_restricted(libinput, devnode, O_RDWR | O_NONBLOCK); + fd = open_restricted(libinput, devnode, + O_RDWR | O_NONBLOCK | O_CLOEXEC); if (fd < 0) return -errno; diff --git a/src/evdev.h b/src/evdev.h index c49186bc..19454ec8 100644 --- a/src/evdev.h +++ b/src/evdev.h @@ -36,6 +36,13 @@ /* The HW DPI rate we normalize to before calculating pointer acceleration */ #define DEFAULT_MOUSE_DPI 1000 + +/* + * The constant (linear) acceleration factor we use to normalize trackpoint + * deltas before calculating pointer acceleration. + */ +#define DEFAULT_TRACKPOINT_ACCEL 1.0 + /* The fake resolution value for abs devices without resolution */ #define EVDEV_FAKE_RESOLUTION 1 @@ -64,6 +71,39 @@ enum evdev_device_tags { EVDEV_TAG_TOUCHPAD_TRACKPOINT = (1 << 3), }; +enum evdev_middlebutton_state { + MIDDLEBUTTON_IDLE, + MIDDLEBUTTON_LEFT_DOWN, + MIDDLEBUTTON_RIGHT_DOWN, + MIDDLEBUTTON_MIDDLE, + MIDDLEBUTTON_LEFT_UP_PENDING, + MIDDLEBUTTON_RIGHT_UP_PENDING, + MIDDLEBUTTON_IGNORE_LR, + MIDDLEBUTTON_IGNORE_L, + MIDDLEBUTTON_IGNORE_R, + MIDDLEBUTTON_PASSTHROUGH, +}; + +enum evdev_middlebutton_event { + MIDDLEBUTTON_EVENT_L_DOWN, + MIDDLEBUTTON_EVENT_R_DOWN, + MIDDLEBUTTON_EVENT_OTHER, + MIDDLEBUTTON_EVENT_L_UP, + MIDDLEBUTTON_EVENT_R_UP, + MIDDLEBUTTON_EVENT_TIMEOUT, + MIDDLEBUTTON_EVENT_ALL_UP, +}; + +enum evdev_device_model { + EVDEV_MODEL_DEFAULT, + EVDEV_MODEL_LENOVO_X230, + EVDEV_MODEL_CHROMEBOOK, + EVDEV_MODEL_SYSTEM76_BONOBO, + EVDEV_MODEL_SYSTEM76_GALAGO, + EVDEV_MODEL_SYSTEM76_KUDU, + EVDEV_MODEL_CLEVO_W740SU, +}; + struct mt_slot { int32_t seat_slot; struct device_coords point; @@ -159,8 +199,22 @@ struct evdev_device { void (*change_to_enabled)(struct evdev_device *device); } left_handed; + struct { + struct libinput_device_config_middle_emulation config; + /* middle-button emulation enabled */ + bool enabled; + bool enabled_default; + bool want_enabled; + enum evdev_middlebutton_state state; + struct libinput_timer timer; + uint32_t button_mask; + uint64_t first_event_time; + } middlebutton; + int dpi; /* HW resolution */ struct ratelimit syn_drop_limit; /* ratelimit for SYN_DROPPED logging */ + + enum evdev_device_model model; }; #define EVDEV_UNHANDLED_DEVICE ((struct evdev_device *) 1) @@ -174,6 +228,10 @@ struct evdev_dispatch_interface { struct input_event *event, uint64_t time); + /* Device is being suspended */ + void (*suspend)(struct evdev_dispatch *dispatch, + struct evdev_device *device); + /* Device is being removed (may be NULL) */ void (*remove)(struct evdev_dispatch *dispatch); @@ -283,6 +341,9 @@ evdev_device_get_size(struct evdev_device *device, int evdev_device_has_button(struct evdev_device *device, uint32_t code); +int +evdev_device_has_key(struct evdev_device *device, uint32_t code); + double evdev_device_transform_x(struct evdev_device *device, double x, @@ -315,6 +376,11 @@ evdev_pointer_notify_button(struct evdev_device *device, uint32_t time, int button, enum libinput_button_state state); +void +evdev_pointer_notify_physical_button(struct evdev_device *device, + uint32_t time, + int button, + enum libinput_button_state state); void evdev_init_natural_scroll(struct evdev_device *device); @@ -336,6 +402,17 @@ evdev_device_remove(struct evdev_device *device); void evdev_device_destroy(struct evdev_device *device); +bool +evdev_middlebutton_filter_button(struct evdev_device *device, + uint64_t time, + int button, + enum libinput_button_state state); + +void +evdev_init_middlebutton(struct evdev_device *device, + bool enabled, + bool want_config); + static inline double evdev_convert_to_mm(const struct input_absinfo *absinfo, double v) { diff --git a/src/filter.c b/src/filter.c index dc299286..626cb0aa 100644 --- a/src/filter.c +++ b/src/filter.c @@ -1,4 +1,5 @@ /* + * Copyright © 2006-2009 Simon Thum * Copyright © 2012 Jonas Ådahl * * Permission to use, copy, modify, distribute, and sell this software and @@ -122,7 +123,7 @@ feed_trackers(struct pointer_accelerator *accel, trackers[current].delta.x = 0.0; trackers[current].delta.y = 0.0; trackers[current].time = time; - trackers[current].dir = vector_get_direction(delta->x, delta->y); + trackers[current].dir = normalized_get_direction(*delta); } static struct pointer_tracker * @@ -137,11 +138,9 @@ tracker_by_offset(struct pointer_accelerator *accel, unsigned int offset) static double calculate_tracker_velocity(struct pointer_tracker *tracker, uint64_t time) { - double distance; double tdelta = time - tracker->time + 1; - distance = hypot(tracker->delta.x, tracker->delta.y); - return distance / tdelta; /* units/ms */ + return normalized_length(tracker->delta) / tdelta; /* units/ms */ } static double @@ -260,13 +259,15 @@ accelerator_set_speed(struct motion_filter *filter, assert(speed >= -1.0 && speed <= 1.0); /* delay when accel kicks in */ - accel_filter->threshold = DEFAULT_THRESHOLD - speed/6.0; + accel_filter->threshold = DEFAULT_THRESHOLD - speed / 4.0; + if (accel_filter->threshold < 0.2) + accel_filter->threshold = 0.2; /* adjust max accel factor */ - accel_filter->accel = DEFAULT_ACCELERATION + speed; + accel_filter->accel = DEFAULT_ACCELERATION + speed * 1.5; /* higher speed -> faster to reach max */ - accel_filter->incline = DEFAULT_INCLINE + speed/2.0; + accel_filter->incline = DEFAULT_INCLINE + speed * 0.75; filter->speed = speed; return true; @@ -345,3 +346,40 @@ touchpad_accel_profile_linear(struct motion_filter *filter, return speed_out * TP_MAGIC_SLOWDOWN; } + +double +touchpad_lenovo_x230_accel_profile(struct motion_filter *filter, + void *data, + double speed_in, + uint64_t time) +{ + /* Keep the magic factor from touchpad_accel_profile_linear. */ + const double TP_MAGIC_SLOWDOWN = 0.4; + + /* Those touchpads presents an actual lower resolution that what is + * advertised. We see some jumps from the cursor due to the big steps + * in X and Y when we are receiving data. + * Apply a factor to minimize those jumps at low speed, and try + * keeping the same feeling as regular touchpads at high speed. + * It still feels slower but it is usable at least */ + const double TP_MAGIC_LOW_RES_FACTOR = 4.0; + double speed_out; + struct pointer_accelerator *accel_filter = + (struct pointer_accelerator *)filter; + + double s1, s2; + const double max_accel = accel_filter->accel * + TP_MAGIC_LOW_RES_FACTOR; /* unitless factor */ + const double threshold = accel_filter->threshold / + TP_MAGIC_LOW_RES_FACTOR; /* units/ms */ + const double incline = accel_filter->incline * TP_MAGIC_LOW_RES_FACTOR; + + speed_in *= TP_MAGIC_SLOWDOWN / TP_MAGIC_LOW_RES_FACTOR; + + s1 = min(1, speed_in * 5); + s2 = 1 + (speed_in - threshold) * incline; + + speed_out = min(max_accel, s2 > 1 ? s2 : s1); + + return speed_out * TP_MAGIC_SLOWDOWN / TP_MAGIC_LOW_RES_FACTOR; +} diff --git a/src/filter.h b/src/filter.h index 70363a62..a0538601 100644 --- a/src/filter.h +++ b/src/filter.h @@ -67,4 +67,9 @@ touchpad_accel_profile_linear(struct motion_filter *filter, void *data, double speed_in, uint64_t time); +double +touchpad_lenovo_x230_accel_profile(struct motion_filter *filter, + void *data, + double speed_in, + uint64_t time); #endif /* FILTER_H */ diff --git a/src/libinput-private.h b/src/libinput-private.h index d1da276d..ae20f807 100644 --- a/src/libinput-private.h +++ b/src/libinput-private.h @@ -24,6 +24,7 @@ #define LIBINPUT_PRIVATE_H #include +#include #include "linux/input.h" @@ -39,6 +40,14 @@ struct device_coords { int x, y; }; +/* + * A coordinate pair in device coordinates, capable of holding non discrete + * values, this is necessary e.g. when device coordinates get averaged. + */ +struct device_float_coords { + double x, y; +}; + /* A dpi-normalized coordinate pair */ struct normalized_coords { double x, y; @@ -173,6 +182,17 @@ struct libinput_device_config_click_method { enum libinput_config_click_method (*get_default_method)(struct libinput_device *device); }; +struct libinput_device_config_middle_emulation { + int (*available)(struct libinput_device *device); + enum libinput_config_status (*set)( + struct libinput_device *device, + enum libinput_config_middle_emulation_state); + enum libinput_config_middle_emulation_state (*get)( + struct libinput_device *device); + enum libinput_config_middle_emulation_state (*get_default)( + struct libinput_device *device); +}; + struct libinput_device_config { struct libinput_device_config_tap *tap; struct libinput_device_config_calibration *calibration; @@ -182,6 +202,7 @@ struct libinput_device_config { struct libinput_device_config_left_handed *left_handed; struct libinput_device_config_scroll_method *scroll_method; struct libinput_device_config_click_method *click_method; + struct libinput_device_config_middle_emulation *middle_emulation; }; struct libinput_device_group { @@ -393,4 +414,85 @@ libinput_now(struct libinput *libinput) return ts.tv_sec * 1000ULL + ts.tv_nsec / 1000000; } + +static inline struct device_float_coords +device_delta(struct device_coords a, struct device_coords b) +{ + struct device_float_coords delta; + + delta.x = a.x - b.x; + delta.y = a.y - b.y; + + return delta; +} + +static inline double +normalized_length(struct normalized_coords norm) +{ + return hypot(norm.x, norm.y); +} + +static inline int +normalized_is_zero(struct normalized_coords norm) +{ + return norm.x == 0.0 && norm.y == 0.0; +} + +enum directions { + N = 1 << 0, + NE = 1 << 1, + E = 1 << 2, + SE = 1 << 3, + S = 1 << 4, + SW = 1 << 5, + W = 1 << 6, + NW = 1 << 7, + UNDEFINED_DIRECTION = 0xff +}; + +static inline int +normalized_get_direction(struct normalized_coords norm) +{ + int dir = UNDEFINED_DIRECTION; + int d1, d2; + double r; + + if (fabs(norm.x) < 2.0 && fabs(norm.y) < 2.0) { + if (norm.x > 0.0 && norm.y > 0.0) + dir = S | SE | E; + else if (norm.x > 0.0 && norm.y < 0.0) + dir = N | NE | E; + else if (norm.x < 0.0 && norm.y > 0.0) + dir = S | SW | W; + else if (norm.x < 0.0 && norm.y < 0.0) + dir = N | NW | W; + else if (norm.x > 0.0) + dir = NE | E | SE; + else if (norm.x < 0.0) + dir = NW | W | SW; + else if (norm.y > 0.0) + dir = SE | S | SW; + else if (norm.y < 0.0) + dir = NE | N | NW; + } else { + /* Calculate r within the interval [0 to 8) + * + * r = [0 .. 2π] where 0 is North + * d_f = r / 2π ([0 .. 1)) + * d_8 = 8 * d_f + */ + r = atan2(norm.y, norm.x); + r = fmod(r + 2.5*M_PI, 2*M_PI); + r *= 4*M_1_PI; + + /* Mark one or two close enough octants */ + d1 = (int)(r + 0.9) % 8; + d2 = (int)(r + 0.1) % 8; + + dir = (1 << d1) | (1 << d2); + } + + return dir; +} + #endif /* LIBINPUT_PRIVATE_H */ diff --git a/src/libinput-util.c b/src/libinput-util.c index 49e297af..4857435b 100644 --- a/src/libinput-util.c +++ b/src/libinput-util.c @@ -29,6 +29,7 @@ #include "config.h" #include +#include #include #include #include @@ -201,3 +202,33 @@ parse_mouse_wheel_click_angle_property(const char *prop) return angle; } + +/** + * Helper function to parse the TRACKPOINT_CONST_ACCEL property from udev. + * Property is of the form: + * TRACKPOINT_CONST_ACCEL= + * + * @param prop The value of the udev property (without the TRACKPOINT_CONST_ACCEL=) + * @return The acceleration, or 0.0 on error. + */ +double +parse_trackpoint_accel_property(const char *prop) +{ + locale_t c_locale; + double accel; + char *endp; + + /* Create a "C" locale to force strtod to use '.' as separator */ + c_locale = newlocale(LC_NUMERIC_MASK, "C", (locale_t)0); + if (c_locale == (locale_t)0) + return 0.0; + + accel = strtod_l(prop, &endp, c_locale); + + freelocale(c_locale); + + if (*endp != '\0') + return 0.0; + + return accel; +} diff --git a/src/libinput-util.h b/src/libinput-util.h index 7e19bf0e..28deae72 100644 --- a/src/libinput-util.h +++ b/src/libinput-util.h @@ -122,63 +122,6 @@ msleep(unsigned int ms) usleep(ms * 1000); } -enum directions { - N = 1 << 0, - NE = 1 << 1, - E = 1 << 2, - SE = 1 << 3, - S = 1 << 4, - SW = 1 << 5, - W = 1 << 6, - NW = 1 << 7, - UNDEFINED_DIRECTION = 0xff -}; - -static inline int -vector_get_direction(double dx, double dy) -{ - int dir = UNDEFINED_DIRECTION; - int d1, d2; - double r; - - if (fabs(dx) < 2.0 && fabs(dy) < 2.0) { - if (dx > 0.0 && dy > 0.0) - dir = S | SE | E; - else if (dx > 0.0 && dy < 0.0) - dir = N | NE | E; - else if (dx < 0.0 && dy > 0.0) - dir = S | SW | W; - else if (dx < 0.0 && dy < 0.0) - dir = N | NW | W; - else if (dx > 0.0) - dir = NE | E | SE; - else if (dx < 0.0) - dir = NW | W | SW; - else if (dy > 0.0) - dir = SE | S | SW; - else if (dy < 0.0) - dir = NE | N | NW; - } else { - /* Calculate r within the interval [0 to 8) - * - * r = [0 .. 2π] where 0 is North - * d_f = r / 2π ([0 .. 1)) - * d_8 = 8 * d_f - */ - r = atan2(dy, dx); - r = fmod(r + 2.5*M_PI, 2*M_PI); - r *= 4*M_1_PI; - - /* Mark one or two close enough octants */ - d1 = (int)(r + 0.9) % 8; - d2 = (int)(r + 0.1) % 8; - - dir = (1 << d1) | (1 << d2); - } - - return dir; -} - static inline int long_bit_is_set(const unsigned long *array, int bit) { @@ -323,5 +266,6 @@ enum ratelimit_state ratelimit_test(struct ratelimit *r); int parse_mouse_dpi_property(const char *prop); int parse_mouse_wheel_click_angle_property(const char *prop); +double parse_trackpoint_accel_property(const char *prop); #endif /* LIBINPUT_UTIL_H */ diff --git a/src/libinput.c b/src/libinput.c index 144e9506..61e2b6b2 100644 --- a/src/libinput.c +++ b/src/libinput.c @@ -1326,6 +1326,35 @@ notify_removed_device(struct libinput_device *device) &removed_device_event->base); } +static inline bool +device_has_cap(struct libinput_device *device, + enum libinput_device_capability cap) +{ + const char *capability; + + if (libinput_device_has_capability(device, cap)) + return true; + + switch (cap) { + case LIBINPUT_DEVICE_CAP_POINTER: + capability = "CAP_POINTER"; + break; + case LIBINPUT_DEVICE_CAP_KEYBOARD: + capability = "CAP_KEYBOARD"; + break; + case LIBINPUT_DEVICE_CAP_TOUCH: + capability = "CAP_TOUCH"; + break; + } + + log_bug_libinput(device->seat->libinput, + "Event for missing capability %s on device \"%s\"\n", + capability, + libinput_device_get_name(device)); + + return false; +} + void keyboard_notify_key(struct libinput_device *device, uint64_t time, @@ -1335,6 +1364,9 @@ keyboard_notify_key(struct libinput_device *device, struct libinput_event_keyboard *key_event; uint32_t seat_key_count; + if (!device_has_cap(device, LIBINPUT_DEVICE_CAP_KEYBOARD)) + return; + key_event = zalloc(sizeof *key_event); if (!key_event) return; @@ -1361,6 +1393,9 @@ pointer_notify_motion(struct libinput_device *device, { struct libinput_event_pointer *motion_event; + if (!device_has_cap(device, LIBINPUT_DEVICE_CAP_POINTER)) + return; + motion_event = zalloc(sizeof *motion_event); if (!motion_event) return; @@ -1383,6 +1418,9 @@ pointer_notify_motion_absolute(struct libinput_device *device, { struct libinput_event_pointer *motion_absolute_event; + if (!device_has_cap(device, LIBINPUT_DEVICE_CAP_POINTER)) + return; + motion_absolute_event = zalloc(sizeof *motion_absolute_event); if (!motion_absolute_event) return; @@ -1406,6 +1444,9 @@ pointer_notify_button(struct libinput_device *device, struct libinput_event_pointer *button_event; int32_t seat_button_count; + if (!device_has_cap(device, LIBINPUT_DEVICE_CAP_POINTER)) + return; + button_event = zalloc(sizeof *button_event); if (!button_event) return; @@ -1436,6 +1477,9 @@ pointer_notify_axis(struct libinput_device *device, { struct libinput_event_pointer *axis_event; + if (!device_has_cap(device, LIBINPUT_DEVICE_CAP_POINTER)) + return; + axis_event = zalloc(sizeof *axis_event); if (!axis_event) return; @@ -1462,6 +1506,9 @@ touch_notify_touch_down(struct libinput_device *device, { struct libinput_event_touch *touch_event; + if (!device_has_cap(device, LIBINPUT_DEVICE_CAP_TOUCH)) + return; + touch_event = zalloc(sizeof *touch_event); if (!touch_event) return; @@ -1487,6 +1534,9 @@ touch_notify_touch_motion(struct libinput_device *device, { struct libinput_event_touch *touch_event; + if (!device_has_cap(device, LIBINPUT_DEVICE_CAP_TOUCH)) + return; + touch_event = zalloc(sizeof *touch_event); if (!touch_event) return; @@ -1511,6 +1561,9 @@ touch_notify_touch_up(struct libinput_device *device, { struct libinput_event_touch *touch_event; + if (!device_has_cap(device, LIBINPUT_DEVICE_CAP_TOUCH)) + return; + touch_event = zalloc(sizeof *touch_event); if (!touch_event) return; @@ -1532,6 +1585,9 @@ touch_notify_frame(struct libinput_device *device, { struct libinput_event_touch *touch_event; + if (!device_has_cap(device, LIBINPUT_DEVICE_CAP_TOUCH)) + return; + touch_event = zalloc(sizeof *touch_event); if (!touch_event) return; @@ -1855,6 +1911,12 @@ libinput_device_pointer_has_button(struct libinput_device *device, uint32_t code return evdev_device_has_button((struct evdev_device *)device, code); } +LIBINPUT_EXPORT int +libinput_device_keyboard_has_key(struct libinput_device *device, uint32_t code) +{ + return evdev_device_has_key((struct evdev_device *)device, code); +} + LIBINPUT_EXPORT struct libinput_event * libinput_event_device_notify_get_base_event(struct libinput_event_device_notify *event) { @@ -2010,11 +2072,12 @@ libinput_device_config_tap_set_enabled(struct libinput_device *device, enable != LIBINPUT_CONFIG_TAP_DISABLED) return LIBINPUT_CONFIG_STATUS_INVALID; - if (enable && - libinput_device_config_tap_get_finger_count(device) == 0) - return LIBINPUT_CONFIG_STATUS_UNSUPPORTED; + if (libinput_device_config_tap_get_finger_count(device) == 0) + return enable ? LIBINPUT_CONFIG_STATUS_UNSUPPORTED : + LIBINPUT_CONFIG_STATUS_SUCCESS; return device->config.tap->set_enabled(device, enable); + } LIBINPUT_EXPORT enum libinput_config_tap_state @@ -2237,9 +2300,6 @@ LIBINPUT_EXPORT enum libinput_config_status libinput_device_config_click_set_method(struct libinput_device *device, enum libinput_config_click_method method) { - if ((libinput_device_config_click_get_methods(device) & method) != method) - return LIBINPUT_CONFIG_STATUS_UNSUPPORTED; - /* Check method is a single valid method */ switch (method) { case LIBINPUT_CONFIG_CLICK_METHOD_NONE: @@ -2250,6 +2310,9 @@ libinput_device_config_click_set_method(struct libinput_device *device, return LIBINPUT_CONFIG_STATUS_INVALID; } + if ((libinput_device_config_click_get_methods(device) & method) != method) + return LIBINPUT_CONFIG_STATUS_UNSUPPORTED; + if (device->config.click_method) return device->config.click_method->set_method(device, method); else /* method must be _NONE to get here */ @@ -2274,6 +2337,60 @@ libinput_device_config_click_get_default_method(struct libinput_device *device) return LIBINPUT_CONFIG_CLICK_METHOD_NONE; } +LIBINPUT_EXPORT int +libinput_device_config_middle_emulation_is_available( + struct libinput_device *device) +{ + if (device->config.middle_emulation) + return device->config.middle_emulation->available(device); + else + return LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED; +} + +LIBINPUT_EXPORT enum libinput_config_status +libinput_device_config_middle_emulation_set_enabled( + struct libinput_device *device, + enum libinput_config_middle_emulation_state enable) +{ + int available = + libinput_device_config_middle_emulation_is_available(device); + + switch (enable) { + case LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED: + if (!available) + return LIBINPUT_CONFIG_STATUS_SUCCESS; + break; + case LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED: + if (!available) + return LIBINPUT_CONFIG_STATUS_UNSUPPORTED; + break; + default: + return LIBINPUT_CONFIG_STATUS_INVALID; + } + + return device->config.middle_emulation->set(device, enable); +} + +LIBINPUT_EXPORT enum libinput_config_middle_emulation_state +libinput_device_config_middle_emulation_get_enabled( + struct libinput_device *device) +{ + if (!libinput_device_config_middle_emulation_is_available(device)) + return LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED; + + return device->config.middle_emulation->get(device); +} + +LIBINPUT_EXPORT enum libinput_config_middle_emulation_state +libinput_device_config_middle_emulation_get_default_enabled( + struct libinput_device *device) +{ + if (!libinput_device_config_middle_emulation_is_available(device)) + return LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED; + + return device->config.middle_emulation->get_default(device); +} + LIBINPUT_EXPORT uint32_t libinput_device_config_scroll_get_methods(struct libinput_device *device) { diff --git a/src/libinput.h b/src/libinput.h index a795e3f9..5bda5fe7 100644 --- a/src/libinput.h +++ b/src/libinput.h @@ -2183,6 +2183,22 @@ libinput_device_get_size(struct libinput_device *device, int libinput_device_pointer_has_button(struct libinput_device *device, uint32_t code); +/** + * @ingroup device + * + * Check if a @ref LIBINPUT_DEVICE_CAP_KEYBOARD device has a key with the + * given code (see linux/input.h). + * + * @param device A current input device + * @param code Key code to check for, e.g. KEY_ESC + * + * @return 1 if the device supports this key code, 0 if it does not, -1 + * on error. + */ +int +libinput_device_keyboard_has_key(struct libinput_device *device, + uint32_t code); + /** * @ingroup device * @@ -2914,6 +2930,130 @@ libinput_device_config_click_get_method(struct libinput_device *device); enum libinput_config_click_method libinput_device_config_click_get_default_method(struct libinput_device *device); +/** + * @ingroup config + */ +enum libinput_config_middle_emulation_state { + /** + * Middle mouse button emulation is to be disabled, or + * is currently disabled. + */ + LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED, + /** + * Middle mouse button emulation is to be enabled, or + * is currently enabled. + */ + LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED, +}; + +/** + * @ingroup config + * + * Check if middle mouse button emulation configuration is available on this + * device. See libinput_device_config_middle_emulation_set_enabled() for + * details. + * + * @note Some devices provide middle mouse button emulation but do not allow + * enabling/disabling that emulation. These devices return zero in + * libinput_device_config_middle_emulation_is_available(). + * + * @param device The device to query + * + * @return Non-zero if middle mouse button emulation is available and can be + * configured, zero otherwise. + * + * @see libinput_device_config_middle_emulation_set_enabled + * @see libinput_device_config_middle_emulation_get_enabled + * @see libinput_device_config_middle_emulation_get_default_enabled + */ +int +libinput_device_config_middle_emulation_is_available( + struct libinput_device *device); + +/** + * @ingroup config + * + * Enable or disable middle button emulation on this device. When enabled, a + * simultaneous press of the left and right button generates a middle mouse + * button event. Releasing the buttons generates a middle mouse button + * release, the left and right button events are discarded otherwise. + * + * The middle button release event may be generated when either button is + * released, or when both buttons have been released. The exact behavior is + * device-dependent. + * + * The middle button emulation behavior when combined with other device + * buttons, including a physical middle button is device-dependent. + * + * @note Some devices provide middle mouse button emulation but do not allow + * enabling/disabling that emulation. + * + * @param device The device to configure + * @param enable @ref LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED to + * disable, @ref LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED To enable + * middle button emulation. + * + * @return A config status code. Disabling middle button emulation on a + * device that does not support middle button emulation always succeeds. + * + * @see libinput_device_config_middle_emulation_is_available + * @see libinput_device_config_middle_emulation_get_enabled + * @see libinput_device_config_middle_emulation_get_default_enabled + */ +enum libinput_config_status +libinput_device_config_middle_emulation_set_enabled( + struct libinput_device *device, + enum libinput_config_middle_emulation_state enable); + +/** + * @ingroup config + * + * Check if configurable middle button emulation is enabled on this device. + * If the device does not have configurable middle button emulation, this + * function returns @ref LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED. + * + * @note Some devices provide middle mouse button emulation but do not allow + * enabling/disabling that emulation. These devices always return @ref + * LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED. + * + * @param device The device to configure + * @return @ref LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED if disabled + * or not available/configurable, @ref + * LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED If enabled. + * + * @see libinput_device_config_middle_emulation_is_available + * @see libinput_device_config_middle_emulation_set_enabled + * @see libinput_device_config_middle_emulation_get_default_enabled + */ +enum libinput_config_middle_emulation_state +libinput_device_config_middle_emulation_get_enabled( + struct libinput_device *device); + +/** + * @ingroup config + * + * Check if configurable middle button emulation is enabled by default on + * this device. If the device does not have configurable middle button + * emulation, this function returns @ref + * LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED. + * + * @note Some devices provide middle mouse button emulation but do not allow + * enabling/disabling that emulation. These devices always return @ref + * LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED. + * + * @param device The device to configure + * @return @ref LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED If disabled + * or not available, @ref LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED if + * enabled. + * + * @see libinput_device_config_middle_emulation_is_available + * @see libinput_device_config_middle_emulation_set_enabled + * @see libinput_device_config_middle_emulation_get_enabled + */ +enum libinput_config_middle_emulation_state +libinput_device_config_middle_emulation_get_default_enabled( + struct libinput_device *device); + /** * @ingroup config * diff --git a/src/libinput.sym b/src/libinput.sym index deab3c20..1ad6eef4 100644 --- a/src/libinput.sym +++ b/src/libinput.sym @@ -129,6 +129,19 @@ local: *; }; +LIBINPUT_0.14.0 { +global: + libinput_device_config_middle_emulation_get_default_enabled; + libinput_device_config_middle_emulation_get_enabled; + libinput_device_config_middle_emulation_is_available; + libinput_device_config_middle_emulation_set_enabled; +} LIBINPUT_0.12.0; + +LIBINPUT_0.15.0 { +global: + libinput_device_keyboard_has_key; +} LIBINPUT_0.14.0; + /* tablet APIs, they are not part of any stable API promise yet. * keep them separate */ LIBINPUT_TABLET_SUPPORT { @@ -154,4 +167,4 @@ LIBINPUT_TABLET_SUPPORT { libinput_tool_ref; libinput_tool_set_user_data; libinput_tool_unref; -} LIBINPUT_0.12.0; +} LIBINPUT_0.15.0; diff --git a/src/path.c b/src/path.c index 832a1fd8..f9b1a6c2 100644 --- a/src/path.c +++ b/src/path.c @@ -310,7 +310,7 @@ udev_device_from_devnode(struct libinput *libinput, dev = udev_device_new_from_devnum(udev, 'c', st.st_rdev); count++; - if (count > 10) { + if (count > 50) { log_bug_libinput(libinput, "udev device never initialized (%s)\n", devnode); diff --git a/src/timer.c b/src/timer.c index f6c8e427..d1d3c108 100644 --- a/src/timer.c +++ b/src/timer.c @@ -101,6 +101,15 @@ libinput_timer_handler(void *data) struct libinput *libinput = data; struct libinput_timer *timer, *tmp; uint64_t now; + uint64_t discard; + int r; + + r = read(libinput->timer.fd, &discard, sizeof(discard)); + if (r == -1 && errno != EAGAIN) + log_bug_libinput(libinput, + "Error %d reading from timerfd (%s)", + errno, + strerror(errno)); now = libinput_now(libinput); if (now == 0) @@ -119,7 +128,8 @@ libinput_timer_handler(void *data) int libinput_timer_subsys_init(struct libinput *libinput) { - libinput->timer.fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC); + libinput->timer.fd = timerfd_create(CLOCK_MONOTONIC, + TFD_CLOEXEC | TFD_NONBLOCK); if (libinput->timer.fd < 0) return -1; diff --git a/test/Makefile.am b/test/Makefile.am index 2875d295..3f28a0a3 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -14,10 +14,14 @@ liblitest_la_SOURCES = \ litest.h \ litest-int.h \ litest-alps-semi-mt.c \ + litest-atmel-hover.c \ litest-bcm5974.c \ litest-generic-singletouch.c \ litest-keyboard.c \ + litest-keyboard-razer-blackwidow.c \ + litest-logitech-trackball.c \ litest-mouse.c \ + litest-mouse-roccat.c \ litest-ms-surface-cover.c \ litest-protocol-a-touch-screen.c \ litest-qemu-usb-tablet.c \ @@ -33,23 +37,31 @@ liblitest_la_SOURCES = \ litest-wacom-isdv4-tablet.c \ litest-wacom-touch.c \ litest-wacom-intuos-finger.c \ + litest-wheel-only.c \ litest-xen-virtual-pointer.c \ litest-vmware-virtual-usb-mouse.c \ litest.c liblitest_la_LIBADD = $(top_builddir)/src/libinput-util.la +liblitest_la_CFLAGS = $(AM_CFLAGS) +if HAVE_LIBUNWIND +liblitest_la_LIBADD += $(LIBUNWIND_LIBS) -ldl +liblitest_la_CFLAGS += $(LIBUNWIND_CFLAGS) +endif run_tests = \ - test-udev \ - test-path \ + test-touchpad \ + test-tablet \ + test-device \ test-pointer \ test-touch \ - test-log \ - test-tablet \ - test-touchpad \ test-trackpoint \ + test-udev \ + test-path \ + test-log \ test-misc \ test-keyboard \ - test-device + test-litest-selftest + build_tests = \ test-build-cxx \ test-build-linker \ @@ -83,7 +95,6 @@ test_log_LDADD = $(TEST_LIBS) test_log_LDFLAGS = -no-install test_tablet_SOURCES = tablet.c -test_tablet_CFLAGS = $(AM_CPPFLAGS) test_tablet_LDADD = $(TEST_LIBS) test_tablet_LDFLAGS = -static @@ -107,6 +118,11 @@ test_device_SOURCES = device.c test_device_LDADD = $(TEST_LIBS) test_device_LDFLAGS = -no-install +test_litest_selftest_SOURCES = litest-selftest.c litest.c litest-int.h litest.h +test_litest_selftest_CFLAGS = -DLITEST_DISABLE_BACKTRACE_LOGGING -DLITEST_NO_MAIN +test_litest_selftest_LDADD = $(TEST_LIBS) +test_litest_selftest_LDFLAGS = -no-install + # build-test only test_build_pedantic_c99_SOURCES = build-pedantic.c test_build_pedantic_c99_CFLAGS = -std=c99 -pedantic -Werror @@ -123,6 +139,8 @@ test_build_linker_LDADD = $(top_builddir)/src/libinput.la $(top_builddir)/src/li test_build_cxx_SOURCES = build-cxx.cc test_build_cxx_CXXFLAGS = -Wall -Wextra -Wno-unused-parameter $(AM_CXXFLAGS) +AM_TESTS_ENVIRONMENT= LITEST_VERBOSE=1; export LITEST_VERBOSE; + if HAVE_VALGRIND VALGRIND_FLAGS=--leak-check=full \ --quiet \ @@ -130,7 +148,7 @@ VALGRIND_FLAGS=--leak-check=full \ --suppressions=$(srcdir)/valgrind.suppressions valgrind: - $(MAKE) check-TESTS LOG_COMPILER="$(VALGRIND)" LOG_FLAGS="$(VALGRIND_FLAGS)" CK_FORK=no + $(MAKE) check-TESTS LOG_COMPILER="$(VALGRIND)" LOG_FLAGS="$(VALGRIND_FLAGS)" CK_FORK=no USING_VALGRIND=yes check: valgrind diff --git a/test/build-pedantic.c b/test/build-pedantic.c index 920fc4ab..f602127d 100644 --- a/test/build-pedantic.c +++ b/test/build-pedantic.c @@ -3,6 +3,7 @@ /* This is a build-test only */ int -main(void) { +main(void) +{ return 0; } diff --git a/test/device.c b/test/device.c index 4c4e2532..193c3ffb 100644 --- a/test/device.c +++ b/test/device.c @@ -313,6 +313,9 @@ START_TEST(device_reenable_syspath_changed) litest_device = litest_add_device(li, LITEST_MOUSE); device2 = litest_device->libinput_device; + /* Note: if the sysname isn't the same, some other device got added + * or removed while this test was running. This is unlikely and + * would result in a false positive, so let's fail the test here */ ck_assert_str_eq(libinput_device_get_sysname(device1), libinput_device_get_sysname(device2)); @@ -814,37 +817,39 @@ START_TEST(abs_mt_device_no_absx) } END_TEST -START_TEST(abs_device_no_range) +static void +assert_device_ignored(struct libinput *li, struct input_absinfo *absinfo) { struct libevdev_uinput *uinput; - struct libinput *li; struct libinput_device *device; - int code; + + uinput = litest_create_uinput_abs_device("test device", NULL, + absinfo, + EV_KEY, BTN_LEFT, + EV_KEY, BTN_RIGHT, + -1); + device = libinput_path_add_device(li, + libevdev_uinput_get_devnode(uinput)); + litest_assert_ptr_null(device); + libevdev_uinput_destroy(uinput); +} + +START_TEST(abs_device_no_range) +{ + struct libinput *li; + int code = _i; /* looped test */ /* set x/y so libinput doesn't just reject for missing axes */ struct input_absinfo absinfo[] = { { ABS_X, 0, 10, 0, 0, 0 }, { ABS_Y, 0, 10, 0, 0, 0 }, - { -1, 0, 0, 0, 0, 0 }, + { code, 0, 0, 0, 0, 0 }, { -1, -1, -1, -1, -1, -1 } }; li = litest_create_context(); litest_disable_log_handler(li); - for (code = 0; code < ABS_MT_SLOT; code++) { - if (code == ABS_MISC) - continue; - absinfo[2].value = code; - uinput = litest_create_uinput_abs_device("test device", NULL, - absinfo, - EV_KEY, BTN_LEFT, - EV_KEY, BTN_RIGHT, - -1); - device = libinput_path_add_device(li, - libevdev_uinput_get_devnode(uinput)); - ck_assert(device == NULL); - libevdev_uinput_destroy(uinput); - } + assert_device_ignored(li, absinfo); litest_restore_log_handler(li); libinput_unref(li); @@ -853,10 +858,8 @@ END_TEST START_TEST(abs_mt_device_no_range) { - struct libevdev_uinput *uinput; struct libinput *li; - struct libinput_device *device; - int code; + int code = _i; /* looped test */ /* set x/y so libinput doesn't just reject for missing axes */ struct input_absinfo absinfo[] = { { ABS_X, 0, 10, 0, 0, 0 }, @@ -865,37 +868,90 @@ START_TEST(abs_mt_device_no_range) { ABS_MT_TRACKING_ID, 0, 255, 0, 0, 0 }, { ABS_MT_POSITION_X, 0, 10, 0, 0, 0 }, { ABS_MT_POSITION_Y, 0, 10, 0, 0, 0 }, - { -1, 0, 0, 0, 0, 0 }, + { code, 0, 0, 0, 0, 0 }, { -1, -1, -1, -1, -1, -1 } }; li = litest_create_context(); litest_disable_log_handler(li); - for (code = ABS_MT_SLOT + 1; code < ABS_CNT; code++) { - if (code == ABS_MT_TOOL_TYPE || - code == ABS_MT_TRACKING_ID) /* kernel overrides it */ - continue; - - absinfo[6].value = code; - uinput = litest_create_uinput_abs_device("test device", NULL, - absinfo, - EV_KEY, BTN_LEFT, - EV_KEY, BTN_RIGHT, - -1); - device = libinput_path_add_device(li, - libevdev_uinput_get_devnode(uinput)); - ck_assert(device == NULL); - libevdev_uinput_destroy(uinput); - } + if (code != ABS_MT_TOOL_TYPE && + code != ABS_MT_TRACKING_ID) /* kernel overrides it */ + assert_device_ignored(li, absinfo); litest_restore_log_handler(li); libinput_unref(li); } END_TEST -int main (int argc, char **argv) +START_TEST(abs_device_missing_res) { + struct libinput *li; + struct input_absinfo absinfo[] = { + { ABS_X, 0, 10, 0, 0, 10 }, + { ABS_Y, 0, 10, 0, 0, 0 }, + { -1, -1, -1, -1, -1, -1 } + }; + + li = litest_create_context(); + litest_disable_log_handler(li); + + assert_device_ignored(li, absinfo); + + absinfo[0].resolution = 0; + absinfo[1].resolution = 20; + + assert_device_ignored(li, absinfo); + + litest_restore_log_handler(li); + libinput_unref(li); +} +END_TEST + +START_TEST(abs_mt_device_missing_res) +{ + struct libinput *li; + struct input_absinfo absinfo[] = { + { ABS_X, 0, 10, 0, 0, 10 }, + { ABS_Y, 0, 10, 0, 0, 10 }, + { ABS_MT_SLOT, 0, 2, 0, 0, 0 }, + { ABS_MT_TRACKING_ID, 0, 255, 0, 0, 0 }, + { ABS_MT_POSITION_X, 0, 10, 0, 0, 10 }, + { ABS_MT_POSITION_Y, 0, 10, 0, 0, 0 }, + { -1, -1, -1, -1, -1, -1 } + }; + + li = litest_create_context(); + litest_disable_log_handler(li); + assert_device_ignored(li, absinfo); + + absinfo[4].resolution = 0; + absinfo[5].resolution = 20; + + assert_device_ignored(li, absinfo); + + litest_restore_log_handler(li); + libinput_unref(li); + +} +END_TEST + +START_TEST(device_wheel_only) +{ + struct litest_device *dev = litest_current_device(); + struct libinput_device *device = dev->libinput_device; + + ck_assert(libinput_device_has_capability(device, + LIBINPUT_DEVICE_CAP_POINTER)); +} +END_TEST + +void +litest_setup_tests(void) +{ + struct range abs_range = { 0, ABS_MISC }; + struct range abs_mt_range = { ABS_MT_SLOT + 1, ABS_CNT }; + litest_add("device:sendevents", device_sendevents_config, LITEST_ANY, LITEST_TOUCHPAD|LITEST_TABLET); litest_add("device:sendevents", device_sendevents_config_invalid, LITEST_ANY, LITEST_TABLET); litest_add("device:sendevents", device_sendevents_config_touchpad, LITEST_TOUCHPAD, LITEST_TABLET); @@ -906,7 +962,6 @@ int main (int argc, char **argv) litest_add("device:sendevents", device_disable_events_pending, LITEST_RELATIVE, LITEST_TOUCHPAD|LITEST_TABLET); litest_add("device:sendevents", device_double_disable, LITEST_ANY, LITEST_TABLET); litest_add("device:sendevents", device_double_enable, LITEST_ANY, LITEST_TABLET); - litest_add_no_device("device:sendevents", device_reenable_syspath_changed); litest_add_no_device("device:sendevents", device_reenable_device_removed); litest_add_for_device("device:sendevents", device_disable_release_buttons, LITEST_MOUSE); @@ -927,8 +982,10 @@ int main (int argc, char **argv) litest_add_no_device("device:invalid devices", abs_device_no_absy); litest_add_no_device("device:invalid devices", abs_mt_device_no_absx); litest_add_no_device("device:invalid devices", abs_mt_device_no_absy); - litest_add_no_device("device:invalid devices", abs_device_no_range); - litest_add_no_device("device:invalid devices", abs_mt_device_no_range); + litest_add_ranged_no_device("device:invalid devices", abs_device_no_range, &abs_range); + litest_add_ranged_no_device("device:invalid devices", abs_mt_device_no_range, &abs_mt_range); + litest_add_no_device("device:invalid devices", abs_device_missing_res); + litest_add_no_device("device:invalid devices", abs_mt_device_missing_res); - return litest_run(argc, argv); + litest_add("device:wheel", device_wheel_only, LITEST_WHEEL, LITEST_RELATIVE|LITEST_ABSOLUTE); } diff --git a/test/keyboard.c b/test/keyboard.c index cb7ad522..1c8092b5 100644 --- a/test/keyboard.c +++ b/test/keyboard.c @@ -61,11 +61,9 @@ START_TEST(keyboard_seat_key_count) continue; } - kev = libinput_event_get_keyboard_event(ev); - ck_assert_notnull(kev); - ck_assert_int_eq(libinput_event_keyboard_get_key(kev), KEY_A); - ck_assert_int_eq(libinput_event_keyboard_get_key_state(kev), - LIBINPUT_KEY_STATE_PRESSED); + kev = litest_is_keyboard_event(ev, + KEY_A, + LIBINPUT_KEY_STATE_PRESSED); ++expected_key_button_count; seat_key_count = @@ -175,31 +173,6 @@ START_TEST(keyboard_ignore_no_pressed_release) } END_TEST -static void -test_key_event(struct litest_device *dev, unsigned int key, int state) -{ - struct libinput *li = dev->libinput; - struct libinput_event *event; - struct libinput_event_keyboard *kevent; - - litest_event(dev, EV_KEY, key, state); - litest_event(dev, EV_SYN, SYN_REPORT, 0); - - libinput_dispatch(li); - - event = libinput_get_event(li); - ck_assert(event != NULL); - ck_assert_int_eq(libinput_event_get_type(event), LIBINPUT_EVENT_KEYBOARD_KEY); - - kevent = libinput_event_get_keyboard_event(event); - ck_assert(kevent != NULL); - ck_assert_int_eq(libinput_event_keyboard_get_key(kevent), key); - ck_assert_int_eq(libinput_event_keyboard_get_key_state(kevent), - state ? LIBINPUT_KEY_STATE_PRESSED : - LIBINPUT_KEY_STATE_RELEASED); - libinput_event_destroy(event); -} - START_TEST(keyboard_key_auto_release) { struct libinput *libinput; @@ -244,7 +217,17 @@ START_TEST(keyboard_key_auto_release) /* Send pressed events, without releasing */ for (i = 0; i < ARRAY_LENGTH(keys); ++i) { - test_key_event(dev, keys[i].code, 1); + key = keys[i].code; + litest_event(dev, EV_KEY, key, 1); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + + libinput_dispatch(libinput); + + event = libinput_get_event(libinput); + kevent = litest_is_keyboard_event(event, + key, + LIBINPUT_KEY_STATE_PRESSED); + libinput_event_destroy(event); } litest_drain_events(libinput); @@ -290,12 +273,49 @@ START_TEST(keyboard_key_auto_release) } END_TEST -int -main(int argc, char **argv) +START_TEST(keyboard_has_key) +{ + struct litest_device *dev = litest_current_device(); + struct libinput_device *device = dev->libinput_device; + unsigned int code; + int evdev_has, libinput_has; + + ck_assert(libinput_device_has_capability( + device, + LIBINPUT_DEVICE_CAP_KEYBOARD)); + + for (code = 0; code < KEY_CNT; code++) { + evdev_has = libevdev_has_event_code(dev->evdev, EV_KEY, code); + libinput_has = libinput_device_keyboard_has_key(device, code); + ck_assert_int_eq(evdev_has, libinput_has); + } +} +END_TEST + +START_TEST(keyboard_keys_bad_device) +{ + struct litest_device *dev = litest_current_device(); + struct libinput_device *device = dev->libinput_device; + unsigned int code; + int has_key; + + if (libinput_device_has_capability(device, + LIBINPUT_DEVICE_CAP_KEYBOARD)) + return; + + for (code = 0; code < KEY_CNT; code++) { + has_key = libinput_device_keyboard_has_key(device, code); + ck_assert_int_eq(has_key, -1); + } +} +END_TEST + +void +litest_setup_tests(void) { litest_add_no_device("keyboard:seat key count", keyboard_seat_key_count); litest_add_no_device("keyboard:key counting", keyboard_ignore_no_pressed_release); litest_add_no_device("keyboard:key counting", keyboard_key_auto_release); - - return litest_run(argc, argv); + litest_add("keyboard:keys", keyboard_has_key, LITEST_KEYS, LITEST_ANY); + litest_add("keyboard:keys", keyboard_keys_bad_device, LITEST_ANY, LITEST_ANY); } diff --git a/test/litest-atmel-hover.c b/test/litest-atmel-hover.c new file mode 100644 index 00000000..7da0901b --- /dev/null +++ b/test/litest-atmel-hover.c @@ -0,0 +1,149 @@ +/* + * Copyright © 2015 Red Hat, Inc. + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#if HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include "libinput-util.h" + +#include "litest.h" +#include "litest-int.h" + +static void +atmel_hover_create(struct litest_device *d); + +static void +litest_atmel_hover_setup(void) +{ + struct litest_device *d = litest_create_device(LITEST_ATMEL_HOVER); + litest_set_current_device(d); +} + +static struct input_event down[] = { + { .type = EV_ABS, .code = ABS_X, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_Y, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_PRESSURE, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_MT_SLOT, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_MT_TRACKING_ID, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_MT_POSITION_X, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_MT_POSITION_Y, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_MT_DISTANCE, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_MT_PRESSURE, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_SYN, .code = SYN_REPORT, .value = 0 }, + { .type = -1, .code = -1 }, +}; + +static struct input_event move[] = { + { .type = EV_ABS, .code = ABS_MT_SLOT, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_X, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_Y, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_PRESSURE, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_MT_POSITION_X, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_MT_POSITION_Y, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_MT_DISTANCE, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_MT_PRESSURE, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_SYN, .code = SYN_REPORT, .value = 0 }, + { .type = -1, .code = -1 }, +}; + +static struct input_event up[] = { + { .type = EV_ABS, .code = ABS_MT_SLOT, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_MT_TRACKING_ID, .value = -1 }, + { .type = EV_ABS, .code = ABS_MT_DISTANCE, .value = 1 }, + { .type = EV_ABS, .code = ABS_MT_PRESSURE, .value = 0 }, + { .type = EV_SYN, .code = SYN_REPORT, .value = 0 }, + { .type = -1, .code = -1 }, +}; + +static struct litest_device_interface interface = { + .touch_down_events = down, + .touch_move_events = move, + .touch_up_events = up, +}; + +static struct input_id input_id = { + .bustype = 0x18, + .vendor = 0x0, + .product = 0x0, +}; + +static int events[] = { + EV_KEY, BTN_LEFT, + EV_KEY, BTN_TOOL_FINGER, + EV_KEY, BTN_TOUCH, + EV_KEY, BTN_TOOL_DOUBLETAP, + EV_KEY, BTN_TOOL_TRIPLETAP, + EV_KEY, BTN_TOOL_QUADTAP, + EV_KEY, BTN_TOOL_QUINTTAP, + INPUT_PROP_MAX, INPUT_PROP_POINTER, + INPUT_PROP_MAX, INPUT_PROP_BUTTONPAD, + -1, -1, +}; + +static struct input_absinfo absinfo[] = { + { ABS_X, 0, 960, 0, 0, 10 }, + { ABS_Y, 0, 540, 0, 0, 10 }, + { ABS_PRESSURE, 0, 255, 0, 0, 0 }, + { ABS_MT_SLOT, 0, 9, 0, 0, 0 }, + { ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0, 0 }, + { ABS_MT_ORIENTATION, 0, 255, 0, 0, 0 }, + { ABS_MT_POSITION_X, 0, 960, 0, 0, 10 }, + { ABS_MT_POSITION_Y, 0, 540, 0, 0, 10 }, + { ABS_MT_TOOL_TYPE, 0, 2, 0, 0, 0 }, + { ABS_MT_TRACKING_ID, 0, 65535, 0, 0, 0 }, + { ABS_MT_PRESSURE, 0, 255, 0, 0, 0 }, + { ABS_MT_DISTANCE, 0, 1, 0, 0, 0 }, + { .value = -1 } +}; + +struct litest_test_device litest_atmel_hover_device = { + .type = LITEST_ATMEL_HOVER, + .features = LITEST_TOUCHPAD | LITEST_BUTTON | LITEST_CLICKPAD | LITEST_HOVER, + .shortname = "atmel hover", + .setup = litest_atmel_hover_setup, + .interface = &interface, + .create = atmel_hover_create, + + .name = "Atmel maXTouch Touchpad", + .id = &input_id, + .events = events, + .absinfo = absinfo, +}; + +static void +atmel_hover_create(struct litest_device *d) +{ + struct litest_semi_mt *semi_mt = zalloc(sizeof(*semi_mt)); + assert(semi_mt); + + d->private = semi_mt; + + d->uinput = litest_create_uinput_device_from_description( + litest_atmel_hover_device.name, + litest_atmel_hover_device.id, + absinfo, + events); + d->interface = &interface; +} diff --git a/test/litest-bcm5974.c b/test/litest-bcm5974.c index 035bed22..ddff0387 100644 --- a/test/litest-bcm5974.c +++ b/test/litest-bcm5974.c @@ -33,7 +33,7 @@ static void litest_bcm5974_setup(void) litest_set_current_device(d); } -struct input_event down[] = { +static struct input_event down[] = { { .type = EV_ABS, .code = ABS_X, .value = LITEST_AUTO_ASSIGN }, { .type = EV_ABS, .code = ABS_Y, .value = LITEST_AUTO_ASSIGN }, { .type = EV_ABS, .code = ABS_PRESSURE, .value = 30 }, diff --git a/test/litest-keyboard-razer-blackwidow.c b/test/litest-keyboard-razer-blackwidow.c new file mode 100644 index 00000000..b1e95b3a --- /dev/null +++ b/test/litest-keyboard-razer-blackwidow.c @@ -0,0 +1,350 @@ +/* + * Copyright © 2015 Red Hat, Inc. + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#if HAVE_CONFIG_H +#include "config.h" +#endif + +#include "litest.h" +#include "litest-int.h" + +/* Recording from https://bugs.freedesktop.org/show_bug.cgi?id=89783 + * This is the second of 4 devices exported by this keyboard, the first is + * just a basic keyboard that is identical to the normal litest-keyboard.c + * file. + */ + +static void litest_blackwidow_setup(void) +{ + struct litest_device *d = litest_create_device(LITEST_KEYBOARD_BLACKWIDOW); + litest_set_current_device(d); +} + +static struct input_id input_id = { + .bustype = 0x3, + .vendor = 0x1532, + .product = 0x11b, +}; + +static int events[] = { + EV_REL, REL_HWHEEL, + EV_KEY, KEY_ESC, + EV_KEY, KEY_1, + EV_KEY, KEY_2, + EV_KEY, KEY_3, + EV_KEY, KEY_4, + EV_KEY, KEY_5, + EV_KEY, KEY_6, + EV_KEY, KEY_7, + EV_KEY, KEY_8, + EV_KEY, KEY_9, + EV_KEY, KEY_0, + EV_KEY, KEY_MINUS, + EV_KEY, KEY_EQUAL, + EV_KEY, KEY_BACKSPACE, + EV_KEY, KEY_TAB, + EV_KEY, KEY_Q, + EV_KEY, KEY_W, + EV_KEY, KEY_E, + EV_KEY, KEY_R, + EV_KEY, KEY_T, + EV_KEY, KEY_Y, + EV_KEY, KEY_U, + EV_KEY, KEY_I, + EV_KEY, KEY_O, + EV_KEY, KEY_P, + EV_KEY, KEY_LEFTBRACE, + EV_KEY, KEY_RIGHTBRACE, + EV_KEY, KEY_ENTER, + EV_KEY, KEY_LEFTCTRL, + EV_KEY, KEY_A, + EV_KEY, KEY_S, + EV_KEY, KEY_D, + EV_KEY, KEY_F, + EV_KEY, KEY_G, + EV_KEY, KEY_H, + EV_KEY, KEY_J, + EV_KEY, KEY_K, + EV_KEY, KEY_L, + EV_KEY, KEY_SEMICOLON, + EV_KEY, KEY_APOSTROPHE, + EV_KEY, KEY_GRAVE, + EV_KEY, KEY_LEFTSHIFT, + EV_KEY, KEY_BACKSLASH, + EV_KEY, KEY_Z, + EV_KEY, KEY_X, + EV_KEY, KEY_C, + EV_KEY, KEY_V, + EV_KEY, KEY_B, + EV_KEY, KEY_N, + EV_KEY, KEY_M, + EV_KEY, KEY_COMMA, + EV_KEY, KEY_DOT, + EV_KEY, KEY_SLASH, + EV_KEY, KEY_RIGHTSHIFT, + EV_KEY, KEY_KPASTERISK, + EV_KEY, KEY_LEFTALT, + EV_KEY, KEY_SPACE, + EV_KEY, KEY_CAPSLOCK, + EV_KEY, KEY_F1, + EV_KEY, KEY_F2, + EV_KEY, KEY_F3, + EV_KEY, KEY_F4, + EV_KEY, KEY_F5, + EV_KEY, KEY_F6, + EV_KEY, KEY_F7, + EV_KEY, KEY_F8, + EV_KEY, KEY_F9, + EV_KEY, KEY_F10, + EV_KEY, KEY_NUMLOCK, + EV_KEY, KEY_SCROLLLOCK, + EV_KEY, KEY_KP7, + EV_KEY, KEY_KP8, + EV_KEY, KEY_KP9, + EV_KEY, KEY_KPMINUS, + EV_KEY, KEY_KP4, + EV_KEY, KEY_KP5, + EV_KEY, KEY_KP6, + EV_KEY, KEY_KPPLUS, + EV_KEY, KEY_KP1, + EV_KEY, KEY_KP2, + EV_KEY, KEY_KP3, + EV_KEY, KEY_KP0, + EV_KEY, KEY_KPDOT, + EV_KEY, KEY_ZENKAKUHANKAKU, + EV_KEY, KEY_102ND, + EV_KEY, KEY_F11, + EV_KEY, KEY_F12, + EV_KEY, KEY_RO, + EV_KEY, KEY_KATAKANA, + EV_KEY, KEY_HIRAGANA, + EV_KEY, KEY_HENKAN, + EV_KEY, KEY_KATAKANAHIRAGANA, + EV_KEY, KEY_MUHENKAN, + EV_KEY, KEY_KPJPCOMMA, + EV_KEY, KEY_KPENTER, + EV_KEY, KEY_RIGHTCTRL, + EV_KEY, KEY_KPSLASH, + EV_KEY, KEY_SYSRQ, + EV_KEY, KEY_RIGHTALT, + EV_KEY, KEY_HOME, + EV_KEY, KEY_UP, + EV_KEY, KEY_PAGEUP, + EV_KEY, KEY_LEFT, + EV_KEY, KEY_RIGHT, + EV_KEY, KEY_END, + EV_KEY, KEY_DOWN, + EV_KEY, KEY_PAGEDOWN, + EV_KEY, KEY_INSERT, + EV_KEY, KEY_DELETE, + EV_KEY, KEY_MUTE, + EV_KEY, KEY_VOLUMEDOWN, + EV_KEY, KEY_VOLUMEUP, + EV_KEY, KEY_POWER, + EV_KEY, KEY_KPEQUAL, + EV_KEY, KEY_PAUSE, + EV_KEY, KEY_KPCOMMA, + EV_KEY, KEY_HANGEUL, + EV_KEY, KEY_HANJA, + EV_KEY, KEY_YEN, + EV_KEY, KEY_LEFTMETA, + EV_KEY, KEY_RIGHTMETA, + EV_KEY, KEY_COMPOSE, + EV_KEY, KEY_STOP, + EV_KEY, KEY_AGAIN, + EV_KEY, KEY_PROPS, + EV_KEY, KEY_UNDO, + EV_KEY, KEY_FRONT, + EV_KEY, KEY_COPY, + EV_KEY, KEY_OPEN, + EV_KEY, KEY_PASTE, + EV_KEY, KEY_FIND, + EV_KEY, KEY_CUT, + EV_KEY, KEY_HELP, + EV_KEY, KEY_MENU, + EV_KEY, KEY_CALC, + EV_KEY, KEY_SLEEP, + EV_KEY, KEY_WAKEUP, + EV_KEY, KEY_FILE, + EV_KEY, KEY_WWW, + EV_KEY, KEY_COFFEE, + EV_KEY, KEY_MAIL, + EV_KEY, KEY_BOOKMARKS, + EV_KEY, KEY_BACK, + EV_KEY, KEY_FORWARD, + EV_KEY, KEY_EJECTCD, + EV_KEY, KEY_NEXTSONG, + EV_KEY, KEY_PLAYPAUSE, + EV_KEY, KEY_PREVIOUSSONG, + EV_KEY, KEY_STOPCD, + EV_KEY, KEY_RECORD, + EV_KEY, KEY_REWIND, + EV_KEY, KEY_PHONE, + EV_KEY, KEY_CONFIG, + EV_KEY, KEY_HOMEPAGE, + EV_KEY, KEY_REFRESH, + EV_KEY, KEY_EXIT, + EV_KEY, KEY_EDIT, + EV_KEY, KEY_SCROLLUP, + EV_KEY, KEY_SCROLLDOWN, + EV_KEY, KEY_KPLEFTPAREN, + EV_KEY, KEY_KPRIGHTPAREN, + EV_KEY, KEY_NEW, + EV_KEY, KEY_F13, + EV_KEY, KEY_F14, + EV_KEY, KEY_F15, + EV_KEY, KEY_F16, + EV_KEY, KEY_F17, + EV_KEY, KEY_F18, + EV_KEY, KEY_F19, + EV_KEY, KEY_F20, + EV_KEY, KEY_F21, + EV_KEY, KEY_F22, + EV_KEY, KEY_F23, + EV_KEY, KEY_F24, + EV_KEY, KEY_CLOSE, + EV_KEY, KEY_PLAY, + EV_KEY, KEY_FASTFORWARD, + EV_KEY, KEY_BASSBOOST, + EV_KEY, KEY_PRINT, + EV_KEY, KEY_CAMERA, + EV_KEY, KEY_CHAT, + EV_KEY, KEY_SEARCH, + EV_KEY, KEY_FINANCE, + EV_KEY, KEY_BRIGHTNESSDOWN, + EV_KEY, KEY_BRIGHTNESSUP, + EV_KEY, KEY_KBDILLUMTOGGLE, + EV_KEY, KEY_SAVE, + EV_KEY, KEY_DOCUMENTS, + EV_KEY, KEY_UNKNOWN, + EV_KEY, KEY_VIDEO_NEXT, + EV_KEY, KEY_BRIGHTNESS_AUTO, + EV_KEY, BTN_0, + EV_KEY, KEY_SELECT, + EV_KEY, KEY_GOTO, + EV_KEY, KEY_INFO, + EV_KEY, KEY_PROGRAM, + EV_KEY, KEY_PVR, + EV_KEY, KEY_SUBTITLE, + EV_KEY, KEY_ZOOM, + EV_KEY, KEY_KEYBOARD, + EV_KEY, KEY_PC, + EV_KEY, KEY_TV, + EV_KEY, KEY_TV2, + EV_KEY, KEY_VCR, + EV_KEY, KEY_VCR2, + EV_KEY, KEY_SAT, + EV_KEY, KEY_CD, + EV_KEY, KEY_TAPE, + EV_KEY, KEY_TUNER, + EV_KEY, KEY_PLAYER, + EV_KEY, KEY_DVD, + EV_KEY, KEY_AUDIO, + EV_KEY, KEY_VIDEO, + EV_KEY, KEY_MEMO, + EV_KEY, KEY_CALENDAR, + EV_KEY, KEY_RED, + EV_KEY, KEY_GREEN, + EV_KEY, KEY_YELLOW, + EV_KEY, KEY_BLUE, + EV_KEY, KEY_CHANNELUP, + EV_KEY, KEY_CHANNELDOWN, + EV_KEY, KEY_LAST, + EV_KEY, KEY_NEXT, + EV_KEY, KEY_RESTART, + EV_KEY, KEY_SLOW, + EV_KEY, KEY_SHUFFLE, + EV_KEY, KEY_PREVIOUS, + EV_KEY, KEY_VIDEOPHONE, + EV_KEY, KEY_GAMES, + EV_KEY, KEY_ZOOMIN, + EV_KEY, KEY_ZOOMOUT, + EV_KEY, KEY_ZOOMRESET, + EV_KEY, KEY_WORDPROCESSOR, + EV_KEY, KEY_EDITOR, + EV_KEY, KEY_SPREADSHEET, + EV_KEY, KEY_GRAPHICSEDITOR, + EV_KEY, KEY_PRESENTATION, + EV_KEY, KEY_DATABASE, + EV_KEY, KEY_NEWS, + EV_KEY, KEY_VOICEMAIL, + EV_KEY, KEY_ADDRESSBOOK, + EV_KEY, KEY_MESSENGER, + EV_KEY, KEY_DISPLAYTOGGLE, + EV_KEY, KEY_SPELLCHECK, + EV_KEY, KEY_LOGOFF, + EV_KEY, KEY_MEDIA_REPEAT, + EV_KEY, KEY_IMAGES, + EV_KEY, KEY_BUTTONCONFIG, + EV_KEY, KEY_TASKMANAGER, + EV_KEY, KEY_JOURNAL, + EV_KEY, KEY_CONTROLPANEL, + EV_KEY, KEY_APPSELECT, + EV_KEY, KEY_SCREENSAVER, + EV_KEY, KEY_VOICECOMMAND, + EV_KEY, KEY_BRIGHTNESS_MIN, + EV_KEY, KEY_BRIGHTNESS_MAX, + EV_MSC, MSC_SCAN, + -1 , -1, +}; + +static struct input_absinfo absinfo[] = { + { ABS_VOLUME, 0, 572, 0, 0, 0 }, + { ABS_MISC, 0, 255, 0, 0, 0 }, + { 0x29, 0, 255, 0, 0, 0 }, + { 0x2a, 0, 255, 0, 0, 0 }, + { 0x2b, 0, 255, 0, 0, 0 }, + { 0x2c, 0, 255, 0, 0, 0 }, + { 0x2d, 0, 255, 0, 0, 0 }, + { 0x2e, 0, 255, 0, 0, 0 }, + { 0x2f, 0, 255, 0, 0, 0 }, + { 0x30, 0, 255, 0, 0, 0 }, + { 0x31, 0, 255, 0, 0, 0 }, + { 0x32, 0, 255, 0, 0, 0 }, + { 0x33, 0, 255, 0, 0, 0 }, + { 0x34, 0, 255, 0, 0, 0 }, + { 0x35, 0, 255, 0, 0, 0 }, + { 0x36, 0, 255, 0, 0, 0 }, + { 0x37, 0, 255, 0, 0, 0 }, + { 0x38, 0, 255, 0, 0, 0 }, + { 0x39, 0, 255, 0, 0, 0 }, + { 0x3a, 0, 255, 0, 0, 0 }, + { 0x3b, 0, 255, 0, 0, 0 }, + { 0x3c, 0, 255, 0, 0, 0 }, + { 0x3d, 0, 255, 0, 0, 0 }, + { 0x3e, 0, 255, 0, 0, 0 }, + { 0x3f, 0, 255, 0, 0, 0 }, + { .value = -1 }, +}; + +struct litest_test_device litest_keyboard_blackwidow_device = { + .type = LITEST_KEYBOARD_BLACKWIDOW, + .features = LITEST_KEYS | LITEST_WHEEL, + .shortname = "blackwidow", + .setup = litest_blackwidow_setup, + .interface = NULL, + + .name = "Razer Razer BlackWidow 2013", + .id = &input_id, + .absinfo = absinfo, + .events = events, +}; diff --git a/test/litest-logitech-trackball.c b/test/litest-logitech-trackball.c new file mode 100644 index 00000000..5e862d86 --- /dev/null +++ b/test/litest-logitech-trackball.c @@ -0,0 +1,64 @@ +/* + * Copyright © 2015 Red Hat, Inc. + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#if HAVE_CONFIG_H +#include "config.h" +#endif + +#include "litest.h" +#include "litest-int.h" + +static void litest_logitech_trackball_setup(void) +{ + struct litest_device *d = litest_create_device(LITEST_LOGITECH_TRACKBALL); + litest_set_current_device(d); +} + +static struct input_id input_id = { + .bustype = 0x3, + .vendor = 0x46d, + .product = 0xc408, +}; + +static int events[] = { + EV_KEY, BTN_LEFT, + EV_KEY, BTN_RIGHT, + EV_KEY, BTN_MIDDLE, + EV_KEY, BTN_SIDE, + EV_KEY, BTN_EXTRA, + EV_REL, REL_X, + EV_REL, REL_Y, + -1 , -1, +}; + +struct litest_test_device litest_logitech_trackball_device = { + .type = LITEST_LOGITECH_TRACKBALL, + .features = LITEST_RELATIVE | LITEST_BUTTON, + .shortname = "logitech trackball", + .setup = litest_logitech_trackball_setup, + .interface = NULL, + + .name = "Logitech USB Trackball", + .id = &input_id, + .absinfo = NULL, + .events = events, +}; diff --git a/test/litest-mouse-roccat.c b/test/litest-mouse-roccat.c new file mode 100644 index 00000000..115e970d --- /dev/null +++ b/test/litest-mouse-roccat.c @@ -0,0 +1,205 @@ +/* + * Copyright © 2015 Red Hat, Inc. + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#if HAVE_CONFIG_H +#include "config.h" +#endif + +#include "litest.h" +#include "litest-int.h" + +static void litest_mouse_roccat_setup(void) +{ + struct litest_device *d = litest_create_device(LITEST_MOUSE_ROCCAT); + litest_set_current_device(d); +} + +static struct input_id input_id = { + .bustype = 0x3, + .vendor = 0x1e7d, + .product = 0x2e22, +}; + +static int events[] = { + EV_REL, REL_X, + EV_REL, REL_Y, + EV_REL, REL_WHEEL, + EV_REL, REL_HWHEEL, + EV_REL, REL_DIAL, + EV_KEY, KEY_ESC, + EV_KEY, KEY_ENTER, + EV_KEY, KEY_KPMINUS, + EV_KEY, KEY_KPPLUS, + EV_KEY, KEY_UP, + EV_KEY, KEY_LEFT, + EV_KEY, KEY_RIGHT, + EV_KEY, KEY_DOWN, + EV_KEY, KEY_MUTE, + EV_KEY, KEY_VOLUMEDOWN, + EV_KEY, KEY_VOLUMEUP, + EV_KEY, KEY_POWER, + EV_KEY, KEY_PAUSE, + EV_KEY, KEY_STOP, + EV_KEY, KEY_PROPS, + EV_KEY, KEY_UNDO, + EV_KEY, KEY_COPY, + EV_KEY, KEY_OPEN, + EV_KEY, KEY_PASTE, + EV_KEY, KEY_FIND, + EV_KEY, KEY_CUT, + EV_KEY, KEY_HELP, + EV_KEY, KEY_MENU, + EV_KEY, KEY_CALC, + EV_KEY, KEY_SLEEP, + EV_KEY, KEY_FILE, + EV_KEY, KEY_WWW, + EV_KEY, KEY_COFFEE, + EV_KEY, KEY_MAIL, + EV_KEY, KEY_BOOKMARKS, + EV_KEY, KEY_BACK, + EV_KEY, KEY_FORWARD, + EV_KEY, KEY_EJECTCD, + EV_KEY, KEY_NEXTSONG, + EV_KEY, KEY_PLAYPAUSE, + EV_KEY, KEY_PREVIOUSSONG, + EV_KEY, KEY_STOPCD, + EV_KEY, KEY_RECORD, + EV_KEY, KEY_REWIND, + EV_KEY, KEY_PHONE, + EV_KEY, KEY_CONFIG, + EV_KEY, KEY_HOMEPAGE, + EV_KEY, KEY_REFRESH, + EV_KEY, KEY_EXIT, + EV_KEY, KEY_SCROLLUP, + EV_KEY, KEY_SCROLLDOWN, + EV_KEY, KEY_NEW, + EV_KEY, KEY_CLOSE, + EV_KEY, KEY_PLAY, + EV_KEY, KEY_FASTFORWARD, + EV_KEY, KEY_BASSBOOST, + EV_KEY, KEY_PRINT, + EV_KEY, KEY_CAMERA, + EV_KEY, KEY_CHAT, + EV_KEY, KEY_SEARCH, + EV_KEY, KEY_FINANCE, + EV_KEY, KEY_BRIGHTNESSDOWN, + EV_KEY, KEY_BRIGHTNESSUP, + EV_KEY, KEY_KBDILLUMTOGGLE, + EV_KEY, KEY_SAVE, + EV_KEY, KEY_DOCUMENTS, + EV_KEY, KEY_UNKNOWN, + EV_KEY, KEY_VIDEO_NEXT, + EV_KEY, KEY_BRIGHTNESS_AUTO, + EV_KEY, BTN_0, + EV_KEY, BTN_LEFT, + EV_KEY, BTN_RIGHT, + EV_KEY, BTN_MIDDLE, + EV_KEY, BTN_SIDE, + EV_KEY, BTN_EXTRA, + EV_KEY, KEY_SELECT, + EV_KEY, KEY_GOTO, + EV_KEY, KEY_INFO, + EV_KEY, KEY_PROGRAM, + EV_KEY, KEY_PVR, + EV_KEY, KEY_SUBTITLE, + EV_KEY, KEY_ZOOM, + EV_KEY, KEY_KEYBOARD, + EV_KEY, KEY_PC, + EV_KEY, KEY_TV, + EV_KEY, KEY_TV2, + EV_KEY, KEY_VCR, + EV_KEY, KEY_VCR2, + EV_KEY, KEY_SAT, + EV_KEY, KEY_CD, + EV_KEY, KEY_TAPE, + EV_KEY, KEY_TUNER, + EV_KEY, KEY_PLAYER, + EV_KEY, KEY_DVD, + EV_KEY, KEY_AUDIO, + EV_KEY, KEY_VIDEO, + EV_KEY, KEY_MEMO, + EV_KEY, KEY_CALENDAR, + EV_KEY, KEY_RED, + EV_KEY, KEY_GREEN, + EV_KEY, KEY_YELLOW, + EV_KEY, KEY_BLUE, + EV_KEY, KEY_CHANNELUP, + EV_KEY, KEY_CHANNELDOWN, + EV_KEY, KEY_LAST, + EV_KEY, KEY_NEXT, + EV_KEY, KEY_RESTART, + EV_KEY, KEY_SLOW, + EV_KEY, KEY_SHUFFLE, + EV_KEY, KEY_PREVIOUS, + EV_KEY, KEY_VIDEOPHONE, + EV_KEY, KEY_GAMES, + EV_KEY, KEY_ZOOMIN, + EV_KEY, KEY_ZOOMOUT, + EV_KEY, KEY_ZOOMRESET, + EV_KEY, KEY_WORDPROCESSOR, + EV_KEY, KEY_EDITOR, + EV_KEY, KEY_SPREADSHEET, + EV_KEY, KEY_GRAPHICSEDITOR, + EV_KEY, KEY_PRESENTATION, + EV_KEY, KEY_DATABASE, + EV_KEY, KEY_NEWS, + EV_KEY, KEY_VOICEMAIL, + EV_KEY, KEY_ADDRESSBOOK, + EV_KEY, KEY_MESSENGER, + EV_KEY, KEY_DISPLAYTOGGLE, + EV_KEY, KEY_SPELLCHECK, + EV_KEY, KEY_LOGOFF, + EV_KEY, KEY_MEDIA_REPEAT, + EV_KEY, KEY_IMAGES, + EV_KEY, KEY_BUTTONCONFIG, + EV_KEY, KEY_TASKMANAGER, + EV_KEY, KEY_JOURNAL, + EV_KEY, KEY_CONTROLPANEL, + EV_KEY, KEY_APPSELECT, + EV_KEY, KEY_SCREENSAVER, + EV_KEY, KEY_VOICECOMMAND, + EV_KEY, KEY_BRIGHTNESS_MIN, + EV_KEY, KEY_BRIGHTNESS_MAX, + -1 , -1, +}; + +static struct input_absinfo absinfo[] = { + { ABS_VOLUME, 0, 572, 0, 0, 0 }, + { ABS_MISC, 0, 0, 0, 0, 0 }, + { ABS_MISC + 1, 0, 0, 0, 0, 0 }, + { ABS_MISC + 2, 0, 0, 0, 0, 0 }, + { ABS_MISC + 3, 0, 0, 0, 0, 0 }, + { .value = -1 } +}; + +struct litest_test_device litest_mouse_roccat_device = { + .type = LITEST_MOUSE_ROCCAT, + .features = LITEST_RELATIVE | LITEST_BUTTON | LITEST_WHEEL | LITEST_KEYS, + .shortname = "mouse_roccat", + .setup = litest_mouse_roccat_setup, + .interface = NULL, + + .name = "ROCCAT ROCCAT Kone XTD", + .id = &input_id, + .absinfo = absinfo, + .events = events, +}; diff --git a/test/litest-ms-surface-cover.c b/test/litest-ms-surface-cover.c index 5b9ec233..37fe850c 100644 --- a/test/litest-ms-surface-cover.c +++ b/test/litest-ms-surface-cover.c @@ -35,6 +35,8 @@ litest_ms_surface_cover_setup(void) } static struct input_event down[] = { + { .type = EV_ABS, .code = ABS_X, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_Y, .value = LITEST_AUTO_ASSIGN }, { .type = EV_ABS, .code = ABS_MT_SLOT, .value = LITEST_AUTO_ASSIGN }, { .type = EV_ABS, .code = ABS_MT_TRACKING_ID, .value = LITEST_AUTO_ASSIGN }, { .type = EV_ABS, .code = ABS_MT_POSITION_X, .value = LITEST_AUTO_ASSIGN }, @@ -59,6 +61,8 @@ static struct litest_device_interface interface = { }; static struct input_absinfo absinfo[] = { + { ABS_X, 0, 1022, 0, 0, 12 }, + { ABS_Y, 0, 487, 0, 0, 12 }, { ABS_VOLUME, 0, 1023, 0, 0, 0 }, { ABS_MISC, 0, 255, 0, 0, 0 }, { 41, 0, 255, 0, 0, 0 }, @@ -90,7 +94,7 @@ static struct input_absinfo absinfo[] = { static struct input_id input_id = { .bustype = 0x3, .vendor = 0x45e, - .product = 0x7a9, + .product = 0x7dc, }; static int events[] = { @@ -368,18 +372,17 @@ static int events[] = { EV_LED, LED_NUML, EV_LED, LED_CAPSL, EV_LED, LED_SCROLLL, - EV_REP, REP_DELAY, -1, -1, }; struct litest_test_device litest_ms_surface_cover_device = { .type = LITEST_MS_SURFACE_COVER, - .features = LITEST_KEYBOARD | LITEST_RELATIVE | LITEST_FAKE_MT, + .features = LITEST_KEYS | LITEST_ABSOLUTE | LITEST_RELATIVE | LITEST_FAKE_MT | LITEST_BUTTON | LITEST_WHEEL, .shortname = "MS surface cover", .setup = litest_ms_surface_cover_setup, .interface = &interface, - .name = "MICROSOFT SAM", + .name = "Microsoft Surface Type Cover", .id = &input_id, .events = events, .absinfo = absinfo, diff --git a/test/litest-selftest.c b/test/litest-selftest.c new file mode 100644 index 00000000..f7974770 --- /dev/null +++ b/test/litest-selftest.c @@ -0,0 +1,369 @@ +#include + +#include +#include + +#include "litest.h" + +START_TEST(litest_assert_trigger) +{ + litest_assert(1 == 2); +} +END_TEST + +START_TEST(litest_assert_notrigger) +{ + litest_assert(1 == 1); +} +END_TEST + +START_TEST(litest_assert_msg_trigger) +{ + litest_assert_msg(1 == 2, "1 is not 2\n"); +} +END_TEST + +START_TEST(litest_assert_msg_NULL_trigger) +{ + litest_assert_msg(1 == 2, NULL); +} +END_TEST + +START_TEST(litest_assert_msg_notrigger) +{ + litest_assert_msg(1 == 1, "1 is not 2\n"); + litest_assert_msg(1 == 1, NULL); +} +END_TEST + +START_TEST(litest_abort_msg_trigger) +{ + litest_abort_msg("message\n"); +} +END_TEST + +START_TEST(litest_abort_msg_NULL_trigger) +{ + litest_abort_msg(NULL); +} +END_TEST + +START_TEST(litest_int_eq_trigger) +{ + int a = 10; + int b = 20; + litest_assert_int_eq(a, b); +} +END_TEST + +START_TEST(litest_int_eq_notrigger) +{ + int a = 10; + int b = 10; + litest_assert_int_eq(a, b); +} +END_TEST + +START_TEST(litest_int_ne_trigger) +{ + int a = 10; + int b = 10; + litest_assert_int_ne(a, b); +} +END_TEST + +START_TEST(litest_int_ne_notrigger) +{ + int a = 10; + int b = 20; + litest_assert_int_ne(a, b); +} +END_TEST + +START_TEST(litest_int_lt_trigger_eq) +{ + int a = 10; + int b = 10; + litest_assert_int_lt(a, b); +} +END_TEST + +START_TEST(litest_int_lt_trigger_gt) +{ + int a = 11; + int b = 10; + litest_assert_int_lt(a, b); +} +END_TEST + +START_TEST(litest_int_lt_notrigger) +{ + int a = 10; + int b = 11; + litest_assert_int_lt(a, b); +} +END_TEST + +START_TEST(litest_int_le_trigger) +{ + int a = 11; + int b = 10; + litest_assert_int_le(a, b); +} +END_TEST + +START_TEST(litest_int_le_notrigger) +{ + int a = 10; + int b = 11; + int c = 10; + litest_assert_int_le(a, b); + litest_assert_int_le(a, c); +} +END_TEST + +START_TEST(litest_int_gt_trigger_eq) +{ + int a = 10; + int b = 10; + litest_assert_int_gt(a, b); +} +END_TEST + +START_TEST(litest_int_gt_trigger_lt) +{ + int a = 9; + int b = 10; + litest_assert_int_gt(a, b); +} +END_TEST + +START_TEST(litest_int_gt_notrigger) +{ + int a = 10; + int b = 9; + litest_assert_int_gt(a, b); +} +END_TEST + +START_TEST(litest_int_ge_trigger) +{ + int a = 9; + int b = 10; + litest_assert_int_ge(a, b); +} +END_TEST + +START_TEST(litest_int_ge_notrigger) +{ + int a = 10; + int b = 9; + int c = 10; + litest_assert_int_ge(a, b); + litest_assert_int_ge(a, c); +} +END_TEST + +START_TEST(litest_ptr_eq_notrigger) +{ + int v = 10; + int *a = &v; + int *b = &v; + int c = NULL; + int d = NULL; + + litest_assert_ptr_eq(a, b); + litest_assert_ptr_eq(c, d); +} +END_TEST + +START_TEST(litest_ptr_eq_trigger) +{ + int v = 10; + int v2 = 11; + int *a = &v; + int *b = &v2; + + litest_assert_ptr_eq(a, b); +} +END_TEST + +START_TEST(litest_ptr_eq_trigger_NULL) +{ + int v = 10; + int *a = &v; + int *b = NULL; + + litest_assert_ptr_eq(a, b); +} +END_TEST + +START_TEST(litest_ptr_eq_trigger_NULL2) +{ + int v = 10; + int *a = &v; + int *b = NULL; + + litest_assert_ptr_eq(b, a); +} +END_TEST + +START_TEST(litest_ptr_ne_trigger) +{ + int v = 10; + int *a = &v; + int *b = &v; + + litest_assert_ptr_ne(a, b); +} +END_TEST + +START_TEST(litest_ptr_ne_trigger_NULL) +{ + int *a = NULL; + + litest_assert_ptr_ne(a, NULL); +} +END_TEST + +START_TEST(litest_ptr_ne_trigger_NULL2) +{ + int *a = NULL; + + litest_assert_ptr_ne(NULL, a); +} +END_TEST + +START_TEST(litest_ptr_ne_notrigger) +{ + int v1 = 10; + int v2 = 10; + int *a = &v1; + int *b = &v2; + int *c = NULL; + + litest_assert_ptr_ne(a, b); + litest_assert_ptr_ne(a, c); + litest_assert_ptr_ne(c, b); +} +END_TEST + +START_TEST(litest_ptr_null_notrigger) +{ + int *a = NULL; + + litest_assert_ptr_null(a); + litest_assert_ptr_null(NULL); +} +END_TEST + +START_TEST(litest_ptr_null_trigger) +{ + int v; + int *a = &v; + + litest_assert_ptr_null(a); +} +END_TEST + +START_TEST(litest_ptr_notnull_notrigger) +{ + int v; + int *a = &v; + + litest_assert_ptr_notnull(a); +} +END_TEST + +START_TEST(litest_ptr_notnull_trigger) +{ + int *a = NULL; + + litest_assert_ptr_notnull(a); +} +END_TEST + +START_TEST(litest_ptr_notnull_trigger_NULL) +{ + litest_assert_ptr_notnull(NULL); +} +END_TEST + +static Suite * +litest_assert_macros_suite(void) +{ + TCase *tc; + Suite *s; + + s = suite_create("litest:assert macros"); + tc = tcase_create("assert"); + tcase_add_test_raise_signal(tc, litest_assert_trigger, SIGABRT); + tcase_add_test(tc, litest_assert_notrigger); + tcase_add_test_raise_signal(tc, litest_assert_msg_trigger, SIGABRT); + tcase_add_test_raise_signal(tc, litest_assert_msg_NULL_trigger, SIGABRT); + tcase_add_test(tc, litest_assert_msg_notrigger); + suite_add_tcase(s, tc); + + tc = tcase_create("abort"); + tcase_add_test_raise_signal(tc, litest_abort_msg_trigger, SIGABRT); + tcase_add_test_raise_signal(tc, litest_abort_msg_NULL_trigger, SIGABRT); + suite_add_tcase(s, tc); + + tc = tcase_create("int comparison "); + tcase_add_test_raise_signal(tc, litest_int_eq_trigger, SIGABRT); + tcase_add_test(tc, litest_int_eq_notrigger); + tcase_add_test_raise_signal(tc, litest_int_ne_trigger, SIGABRT); + tcase_add_test(tc, litest_int_ne_notrigger); + tcase_add_test_raise_signal(tc, litest_int_le_trigger, SIGABRT); + tcase_add_test(tc, litest_int_le_notrigger); + tcase_add_test_raise_signal(tc, litest_int_lt_trigger_gt, SIGABRT); + tcase_add_test_raise_signal(tc, litest_int_lt_trigger_eq, SIGABRT); + tcase_add_test(tc, litest_int_lt_notrigger); + tcase_add_test_raise_signal(tc, litest_int_ge_trigger, SIGABRT); + tcase_add_test(tc, litest_int_ge_notrigger); + tcase_add_test_raise_signal(tc, litest_int_gt_trigger_eq, SIGABRT); + tcase_add_test_raise_signal(tc, litest_int_gt_trigger_lt, SIGABRT); + tcase_add_test(tc, litest_int_gt_notrigger); + suite_add_tcase(s, tc); + + tc = tcase_create("pointer comparison "); + tcase_add_test_raise_signal(tc, litest_ptr_eq_trigger, SIGABRT); + tcase_add_test_raise_signal(tc, litest_ptr_eq_trigger_NULL, SIGABRT); + tcase_add_test_raise_signal(tc, litest_ptr_eq_trigger_NULL2, SIGABRT); + tcase_add_test(tc, litest_ptr_eq_notrigger); + tcase_add_test_raise_signal(tc, litest_ptr_ne_trigger, SIGABRT); + tcase_add_test_raise_signal(tc, litest_ptr_ne_trigger_NULL, SIGABRT); + tcase_add_test_raise_signal(tc, litest_ptr_ne_trigger_NULL2, SIGABRT); + tcase_add_test(tc, litest_ptr_ne_notrigger); + tcase_add_test_raise_signal(tc, litest_ptr_null_trigger, SIGABRT); + tcase_add_test(tc, litest_ptr_null_notrigger); + tcase_add_test_raise_signal(tc, litest_ptr_notnull_trigger, SIGABRT); + tcase_add_test_raise_signal(tc, litest_ptr_notnull_trigger_NULL, SIGABRT); + tcase_add_test(tc, litest_ptr_notnull_notrigger); + suite_add_tcase(s, tc); + + return s; +} + +int +main (int argc, char **argv) +{ + int nfailed; + Suite *s; + SRunner *sr; + + /* when running under valgrind we're using nofork mode, so a signal + * raised by a test will fail in valgrind. There's nothing to + * memcheck here anyway, so just skip the valgrind test */ + if (getenv("USING_VALGRIND")) + return EXIT_SUCCESS; + + s = litest_assert_macros_suite(); + sr = srunner_create(s); + + srunner_run_all(sr, CK_ENV); + nfailed = srunner_ntests_failed(sr); + srunner_free(sr); + + return (nfailed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/test/litest-wheel-only.c b/test/litest-wheel-only.c new file mode 100644 index 00000000..e087dd26 --- /dev/null +++ b/test/litest-wheel-only.c @@ -0,0 +1,68 @@ +/* + * Copyright © 2015 Red Hat, Inc. + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#if HAVE_CONFIG_H +#include "config.h" +#endif + +#include "litest.h" +#include "litest-int.h" + +static void litest_wheel_only_setup(void) +{ + struct litest_device *d = litest_create_device(LITEST_WHEEL_ONLY); + litest_set_current_device(d); +} + +static struct input_id input_id = { + .bustype = 0x3, + .vendor = 0x1, + .product = 0x2, +}; + +static int events[] = { + EV_REL, REL_WHEEL, + -1 , -1, +}; + +static const char udev_rule[] = +"ACTION==\"remove\", GOTO=\"wheel_only_end\"\n" +"KERNEL!=\"event*\", GOTO=\"wheel_only_end\"\n" +"\n" +"ATTRS{name}==\"litest wheel only device*\",\\\n" +" ENV{ID_INPUT_KEY}=\"1\"\n" +"\n" +"LABEL=\"wheel_only_end\""; + +struct litest_test_device litest_wheel_only_device = { + .type = LITEST_WHEEL_ONLY, + .features = LITEST_WHEEL, + .shortname = "wheel only", + .setup = litest_wheel_only_setup, + .interface = NULL, + + .name = "wheel only device", + .id = &input_id, + .absinfo = NULL, + .events = events, + .udev_rule = udev_rule, +}; diff --git a/test/litest.c b/test/litest.c index 9ac7d3c2..60c60115 100644 --- a/test/litest.c +++ b/test/litest.c @@ -1,5 +1,6 @@ /* * Copyright © 2013 Red Hat, Inc. + * Copyright © 2013 Marcin Slusarz * * Permission to use, copy, modify, distribute, and sell this software and its * documentation for any purpose is hereby granted without fee, provided that @@ -29,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -50,6 +52,237 @@ static int in_debugger = -1; static int verbose = 0; +const char *filter_test = NULL; +const char *filter_device = NULL; +const char *filter_group = NULL; + +#ifdef HAVE_LIBUNWIND +#define UNW_LOCAL_ONLY +#include +#include + +/* defined for the litest selftest */ +#ifndef LITEST_DISABLE_BACKTRACE_LOGGING +#define litest_log(...) fprintf(stderr, __VA_ARGS__) +#define litest_vlog(format_, args_) vfprintf(stderr, format_, args_) +#else +#define litest_log(...) /* __VA_ARGS__ */ +#define litest_vlog(...) /* __VA_ARGS__ */ +#endif + +static char cwd[PATH_MAX]; + +static bool +litest_backtrace_get_lineno(const char *executable, + unw_word_t addr, + char *file_return, + int *line_return) +{ +#if HAVE_ADDR2LINE + FILE* f; + char buffer[PATH_MAX]; + char *s; + int i; + + if (!cwd[0]) + getcwd(cwd, sizeof(cwd)); + + sprintf (buffer, + ADDR2LINE " -C -e %s -i %lx", + executable, + (unsigned long) addr); + + f = popen(buffer, "r"); + if (f == NULL) { + litest_log("Failed to execute: %s\n", buffer); + return false; + } + + buffer[0] = '?'; + fgets(buffer, sizeof(buffer), f); + fclose(f); + + if (buffer[0] == '?') + return false; + + s = strrchr(buffer, ':'); + if (!s) + return false; + + *s = '\0'; + s++; + sscanf(s, "%d", line_return); + + /* now strip cwd from buffer */ + s = buffer; + i = 0; + while(cwd[i] == *s) { + *s = '\0'; + s++; + i++; + } + + if (i > 0) + *(--s) = '.'; + strcpy(file_return, s); + + return true; +#else /* HAVE_ADDR2LINE */ + return false; +#endif +} + +static void +litest_backtrace(void) +{ + unw_cursor_t cursor; + unw_context_t context; + unw_word_t off; + unw_proc_info_t pip; + int ret; + char procname[256]; + Dl_info dlinfo; + /* filename and i are unused ifdef LITEST_SHUTUP */ + const char *filename __attribute__((unused)); + int i __attribute__((unused)) = 0; + + pip.unwind_info = NULL; + ret = unw_getcontext(&context); + if (ret) { + litest_log("unw_getcontext failed: %s [%d]\n", + unw_strerror(ret), + ret); + return; + } + + ret = unw_init_local(&cursor, &context); + if (ret) { + litest_log("unw_init_local failed: %s [%d]\n", + unw_strerror(ret), + ret); + return; + } + + litest_log("\nBacktrace:\n"); + ret = unw_step(&cursor); + while (ret > 0) { + char file[PATH_MAX]; + int line; + bool have_lineno = false; + + ret = unw_get_proc_info(&cursor, &pip); + if (ret) { + litest_log("unw_get_proc_info failed: %s [%d]\n", + unw_strerror(ret), + ret); + break; + } + + ret = unw_get_proc_name(&cursor, procname, 256, &off); + if (ret && ret != -UNW_ENOMEM) { + if (ret != -UNW_EUNSPEC) + litest_log("unw_get_proc_name failed: %s [%d]\n", + unw_strerror(ret), + ret); + procname[0] = '?'; + procname[1] = 0; + } + + if (dladdr((void *)(pip.start_ip + off), &dlinfo) && + dlinfo.dli_fname && + *dlinfo.dli_fname) { + filename = dlinfo.dli_fname; + have_lineno = litest_backtrace_get_lineno(filename, + (pip.start_ip + off), + file, + &line); + } else { + filename = "?"; + } + + if (have_lineno) { + litest_log("%u: %s() (%s:%d)\n", + i, + procname, + file, + line); + } else { + litest_log("%u: %s (%s%s+%#x) [%p]\n", + i, + filename, + procname, + ret == -UNW_ENOMEM ? "..." : "", + (int)off, + (void *)(pip.start_ip + off)); + } + + i++; + ret = unw_step(&cursor); + if (ret < 0) + litest_log("unw_step failed: %s [%d]\n", + unw_strerror(ret), + ret); + } + litest_log("\n"); +} +#else /* HAVE_LIBUNWIND */ +static inline void +litest_backtrace(void) +{ + /* thou shall install libunwind */ +} +#endif + +void +litest_fail_condition(const char *file, + int line, + const char *func, + const char *condition, + const char *message, + ...) +{ + litest_log("FAILED: %s\n", condition); + + if (message) { + va_list args; + va_start(args, message); + litest_vlog(message, args); + va_end(args); + } + + litest_log("in %s() (%s:%d)\n", func, file, line); + litest_backtrace(); + abort(); +} + +void +litest_fail_comparison_int(const char *file, + int line, + const char *func, + const char *operator, + int a, + int b, + const char *astr, + const char *bstr) +{ + litest_log("FAILED COMPARISON: %s %s %s\n", astr, operator, bstr); + litest_log("Resolved to: %d %s %d\n", a, operator, b); + litest_log("in %s() (%s:%d)\n", func, file, line); + litest_backtrace(); + abort(); +} + +void +litest_fail_comparison_ptr(const char *file, + int line, + const char *func, + const char *comparison) +{ + litest_log("FAILED COMPARISON: %s\n", comparison); + litest_log("in %s() (%s:%d)\n", func, file, line); + litest_backtrace(); + abort(); +} struct test { struct list node; @@ -67,11 +300,13 @@ struct suite { static struct litest_device *current_device; -struct litest_device *litest_current_device(void) { +struct litest_device *litest_current_device(void) +{ return current_device; } -void litest_set_current_device(struct litest_device *device) { +void litest_set_current_device(struct litest_device *device) +{ current_device = device; } @@ -102,6 +337,12 @@ extern struct litest_test_device litest_synaptics_hover_device; extern struct litest_test_device litest_synaptics_carbon3rd_device; extern struct litest_test_device litest_protocol_a_screen; extern struct litest_test_device litest_wacom_finger_device; +extern struct litest_test_device litest_keyboard_blackwidow_device; +extern struct litest_test_device litest_wheel_only_device; +extern struct litest_test_device litest_mouse_roccat_device; +extern struct litest_test_device litest_ms_surface_cover_device; +extern struct litest_test_device litest_logitech_trackball_device; +extern struct litest_test_device litest_atmel_hover_device; struct litest_test_device* devices[] = { &litest_synaptics_clickpad_device, @@ -125,6 +366,12 @@ struct litest_test_device* devices[] = { &litest_synaptics_carbon3rd_device, &litest_protocol_a_screen, &litest_wacom_finger_device, + &litest_keyboard_blackwidow_device, + &litest_wheel_only_device, + &litest_mouse_roccat_device, + &litest_ms_surface_cover_device, + &litest_logitech_trackball_device, + &litest_atmel_hover_device, NULL, }; @@ -181,8 +428,10 @@ litest_drop_udev_rules(void) static void litest_add_tcase_for_device(struct suite *suite, + const char *funcname, void *func, - const struct litest_test_device *dev) + const struct litest_test_device *dev, + const struct range *range) { struct test *t; const char *test_name = dev->shortname; @@ -191,7 +440,13 @@ litest_add_tcase_for_device(struct suite *suite, if (strcmp(t->name, test_name) != 0) continue; - tcase_add_test(t->tc, func); + if (range) + tcase_add_loop_test(t->tc, + func, + range->lower, + range->upper); + else + tcase_add_test(t->tc, func); return; } @@ -214,16 +469,25 @@ litest_add_tcase_for_device(struct suite *suite, } static void -litest_add_tcase_no_device(struct suite *suite, void *func) +litest_add_tcase_no_device(struct suite *suite, + void *func, + const struct range *range) { struct test *t; const char *test_name = "no device"; + if (filter_device && + fnmatch(filter_device, test_name, 0) != 0) + return; + list_for_each(t, &suite->tests, node) { if (strcmp(t->name, test_name) != 0) continue; - tcase_add_test(t->tc, func); + if (range) + tcase_add_loop_test(t->tc, func, range->lower, range->upper); + else + tcase_add_test(t->tc, func); return; } @@ -236,40 +500,6 @@ litest_add_tcase_no_device(struct suite *suite, void *func) suite_add_tcase(suite->suite, t->tc); } -static void -litest_add_tcase(struct suite *suite, void *func, - enum litest_device_feature required, - enum litest_device_feature excluded) -{ - struct litest_test_device **dev = devices; - - assert(required >= LITEST_DISABLE_DEVICE); - assert(excluded >= LITEST_DISABLE_DEVICE); - - if (required == LITEST_DISABLE_DEVICE && - excluded == LITEST_DISABLE_DEVICE) { - litest_add_tcase_no_device(suite, func); - } else if (required != LITEST_ANY || excluded != LITEST_ANY) { - while (*dev) { - if (((*dev)->features & required) == required && - ((*dev)->features & excluded) == 0) - litest_add_tcase_for_device(suite, func, *dev); - dev++; - } - } else { - while (*dev) { - litest_add_tcase_for_device(suite, func, *dev); - dev++; - } - } -} - -void -litest_add_no_device(const char *name, void *func) -{ - litest_add(name, func, LITEST_DISABLE_DEVICE, LITEST_DISABLE_DEVICE); -} - static struct suite * get_suite(const char *name) { @@ -294,35 +524,151 @@ get_suite(const char *name) return s; } -void -litest_add(const char *name, - void *func, - enum litest_device_feature required, - enum litest_device_feature excluded) +static void +litest_add_tcase(const char *suite_name, + const char *funcname, + void *func, + enum litest_device_feature required, + enum litest_device_feature excluded, + const struct range *range) { - litest_add_tcase(get_suite(name), func, required, excluded); + struct litest_test_device **dev = devices; + struct suite *suite; + + assert(required >= LITEST_DISABLE_DEVICE); + assert(excluded >= LITEST_DISABLE_DEVICE); + + if (filter_test && + fnmatch(filter_test, funcname, 0) != 0) + return; + + if (filter_group && + fnmatch(filter_group, suite_name, 0) != 0) + return; + + suite = get_suite(suite_name); + + if (required == LITEST_DISABLE_DEVICE && + excluded == LITEST_DISABLE_DEVICE) { + litest_add_tcase_no_device(suite, func, range); + } else if (required != LITEST_ANY || excluded != LITEST_ANY) { + for (; *dev; dev++) { + if (filter_device && + fnmatch(filter_device, (*dev)->shortname, 0) != 0) + continue; + if (((*dev)->features & required) != required || + ((*dev)->features & excluded) != 0) + continue; + + litest_add_tcase_for_device(suite, + funcname, + func, + *dev, + range); + } + } else { + for (; *dev; dev++) { + if (filter_device && + fnmatch(filter_device, (*dev)->shortname, 0) != 0) + continue; + + litest_add_tcase_for_device(suite, + funcname, + func, + *dev, + range); + } + } } void -litest_add_for_device(const char *name, - void *func, - enum litest_device_type type) +_litest_add_no_device(const char *name, const char *funcname, void *func) +{ + _litest_add(name, funcname, func, LITEST_DISABLE_DEVICE, LITEST_DISABLE_DEVICE); +} + +void +_litest_add_ranged_no_device(const char *name, + const char *funcname, + void *func, + const struct range *range) +{ + _litest_add_ranged(name, + funcname, + func, + LITEST_DISABLE_DEVICE, + LITEST_DISABLE_DEVICE, + range); +} + +void +_litest_add(const char *name, + const char *funcname, + void *func, + enum litest_device_feature required, + enum litest_device_feature excluded) +{ + _litest_add_ranged(name, + funcname, + func, + required, + excluded, + NULL); +} + +void +_litest_add_ranged(const char *name, + const char *funcname, + void *func, + enum litest_device_feature required, + enum litest_device_feature excluded, + const struct range *range) +{ + litest_add_tcase(name, funcname, func, required, excluded, range); +} + +void +_litest_add_for_device(const char *name, + const char *funcname, + void *func, + enum litest_device_type type) +{ + _litest_add_ranged_for_device(name, funcname, func, type, NULL); +} + +void +_litest_add_ranged_for_device(const char *name, + const char *funcname, + void *func, + enum litest_device_type type, + const struct range *range) { struct suite *s; struct litest_test_device **dev = devices; assert(type < LITEST_NO_DEVICE); + if (filter_group && + fnmatch(filter_group, name, 0) != 0) + return; + s = get_suite(name); - while (*dev) { + for (; *dev; dev++) { + if (filter_device && + fnmatch(filter_device, (*dev)->shortname, 0) != 0) + continue; + if ((*dev)->type == type) { - litest_add_tcase_for_device(s, func, *dev); + litest_add_tcase_for_device(s, + funcname, + func, + *dev, + range); return; } - dev++; } - ck_abort_msg("Invalid test device type"); + litest_abort_msg("Invalid test device type"); } static int @@ -391,7 +737,8 @@ litest_log_handler(struct libinput *libinput, static int open_restricted(const char *path, int flags, void *userdata) { - return open(path, flags); + int fd = open(path, flags); + return fd < 0 ? -errno : fd; } static void @@ -405,14 +752,9 @@ struct libinput_interface interface = { .close_restricted = close_restricted, }; -static const struct option opts[] = { - { "list", 0, 0, 'l' }, - { "verbose", 0, 0, 'v' }, - { 0, 0, 0, 0} -}; - -int -litest_run(int argc, char **argv) { +static inline int +litest_run(int argc, char **argv) +{ struct suite *s, *snext; int failed; SRunner *sr = NULL; @@ -430,26 +772,8 @@ litest_run(int argc, char **argv) { srunner_add_suite(sr, s->suite); } - while(1) { - int c; - int option_index = 0; - - c = getopt_long(argc, argv, "", opts, &option_index); - if (c == -1) - break; - switch(c) { - case 'l': - litest_list_tests(&all_tests); - return 0; - case 'v': - verbose = 1; - break; - default: - fprintf(stderr, "usage: %s [--list]\n", argv[0]); - return 1; - - } - } + if (getenv("LITEST_VERBOSE")) + verbose = 1; srunner_run_all(sr, CK_ENV); failed = srunner_ntests_failed(sr); @@ -484,13 +808,13 @@ merge_absinfo(const struct input_absinfo *orig, return NULL; abs = calloc(sz, sizeof(*abs)); - ck_assert(abs != NULL); + litest_assert(abs != NULL); nelem = 0; while (orig[nelem].value != -1) { abs[nelem] = orig[nelem]; nelem++; - ck_assert_int_lt(nelem, sz); + litest_assert_int_lt(nelem, sz); } /* just append, if the same axis is present twice, libevdev will @@ -498,10 +822,10 @@ merge_absinfo(const struct input_absinfo *orig, i = 0; while (override && override[i].value != -1) { abs[nelem++] = override[i++]; - ck_assert_int_lt(nelem, sz); + litest_assert_int_lt(nelem, sz); } - ck_assert_int_lt(nelem, sz); + litest_assert_int_lt(nelem, sz); abs[nelem].value = -1; return abs; @@ -518,13 +842,13 @@ merge_events(const int *orig, const int *override) return NULL; events = calloc(sz, sizeof(int)); - ck_assert(events != NULL); + litest_assert(events != NULL); nelem = 0; while (orig[nelem] != -1) { events[nelem] = orig[nelem]; nelem++; - ck_assert_int_lt(nelem, sz); + litest_assert_int_lt(nelem, sz); } /* just append, if the same axis is present twice, libevdev will @@ -532,10 +856,10 @@ merge_events(const int *orig, const int *override) i = 0; while (override && override[i] != -1) { events[nelem++] = override[i++]; - ck_assert_int_le(nelem, sz); + litest_assert_int_le(nelem, sz); } - ck_assert_int_lt(nelem, sz); + litest_assert_int_lt(nelem, sz); events[nelem] = -1; return events; @@ -561,13 +885,14 @@ litest_init_udev_rules(struct litest_test_device *dev) UDEV_RULES_D, UDEV_RULE_PREFIX, dev->shortname); - ck_assert_int_eq(rc, - strlen(UDEV_RULES_D) + - strlen(UDEV_RULE_PREFIX) + - strlen(dev->shortname) + 7); + litest_assert_int_eq(rc, + (int)( + strlen(UDEV_RULES_D) + + strlen(UDEV_RULE_PREFIX) + + strlen(dev->shortname) + 7)); f = fopen(path, "w"); - ck_assert_notnull(f); - ck_assert_int_ge(fputs(dev->udev_rule, f), 0); + litest_assert_notnull(f); + litest_assert_int_ge(fputs(dev->udev_rule, f), 0); fclose(f); litest_reload_udev_rules(); @@ -601,7 +926,7 @@ litest_create(enum litest_device_type which, ck_abort_msg("Invalid device type %d\n", which); d = zalloc(sizeof(*d)); - ck_assert(d != NULL); + litest_assert(d != NULL); udev_file = litest_init_udev_rules(*dev); @@ -611,8 +936,7 @@ litest_create(enum litest_device_type which, if (abs_override || events_override) { if (udev_file) unlink(udev_file); - ck_abort_msg("Custom create cannot" - "be overridden"); + litest_abort_msg("Custom create cannot be overridden"); } return d; @@ -641,7 +965,7 @@ litest_create_context(void) { struct libinput *libinput = libinput_path_create_context(&interface, NULL); - ck_assert_notnull(libinput); + litest_assert_notnull(libinput); libinput_log_set_handler(libinput, litest_log_handler); if (verbose) @@ -682,16 +1006,16 @@ litest_add_device_with_overrides(struct libinput *libinput, events_override); path = libevdev_uinput_get_devnode(d->uinput); - ck_assert(path != NULL); + litest_assert(path != NULL); fd = open(path, O_RDWR|O_NONBLOCK); - ck_assert_int_ne(fd, -1); + litest_assert_int_ne(fd, -1); rc = libevdev_new_from_fd(fd, &d->evdev); - ck_assert_int_eq(rc, 0); + litest_assert_int_eq(rc, 0); d->libinput = libinput; d->libinput_device = libinput_path_add_device(d->libinput, path); - ck_assert(d->libinput_device != NULL); + litest_assert(d->libinput_device != NULL); libinput_device_ref(d->libinput_device); if (d->interface) { @@ -786,13 +1110,14 @@ litest_event(struct litest_device *d, unsigned int type, return; ret = libevdev_uinput_write_event(d->uinput, type, code, value); - ck_assert_int_eq(ret, 0); + litest_assert_int_eq(ret, 0); } int litest_auto_assign_value(struct litest_device *d, const struct input_event *ev, - int slot, double x, double y) + int slot, double x, double y, + bool touching) { static int tracking_id; int value = ev->value; @@ -815,6 +1140,9 @@ litest_auto_assign_value(struct litest_device *d, case ABS_MT_SLOT: value = slot; break; + case ABS_MT_DISTANCE: + value = touching ? 0 : 1; + break; } return value; @@ -831,9 +1159,9 @@ send_btntool(struct litest_device *d) litest_event(d, EV_KEY, BTN_TOOL_QUINTTAP, d->ntouches_down == 5); } -void -litest_touch_down(struct litest_device *d, unsigned int slot, - double x, double y) +static void +litest_slot_start(struct litest_device *d, unsigned int slot, + double x, double y, bool touching) { struct input_event *ev; @@ -849,12 +1177,25 @@ litest_touch_down(struct litest_device *d, unsigned int slot, ev = d->interface->touch_down_events; while (ev && (int16_t)ev->type != -1 && (int16_t)ev->code != -1) { - int value = litest_auto_assign_value(d, ev, slot, x, y); + int value = litest_auto_assign_value(d, + ev, + slot, + x, + y, + touching); + litest_event(d, ev->type, ev->code, value); ev++; } } +void +litest_touch_down(struct litest_device *d, unsigned int slot, + double x, double y) +{ + litest_slot_start(d, slot, x, y, 1); +} + void litest_touch_up(struct litest_device *d, unsigned int slot) { @@ -880,15 +1221,20 @@ litest_touch_up(struct litest_device *d, unsigned int slot) ev = up; while (ev && (int16_t)ev->type != -1 && (int16_t)ev->code != -1) { - int value = litest_auto_assign_value(d, ev, slot, 0, 0); + int value = litest_auto_assign_value(d, + ev, + slot, + 0, + 0, + false); litest_event(d, ev->type, ev->code, value); ev++; } } -void -litest_touch_move(struct litest_device *d, unsigned int slot, - double x, double y) +static void +litest_slot_move(struct litest_device *d, unsigned int slot, + double x, double y, bool touching) { struct input_event *ev; @@ -899,12 +1245,24 @@ litest_touch_move(struct litest_device *d, unsigned int slot, ev = d->interface->touch_move_events; while (ev && (int16_t)ev->type != -1 && (int16_t)ev->code != -1) { - int value = litest_auto_assign_value(d, ev, slot, x, y); + int value = litest_auto_assign_value(d, + ev, + slot, + x, + y, + touching); litest_event(d, ev->type, ev->code, value); ev++; } } +void +litest_touch_move(struct litest_device *d, unsigned int slot, + double x, double y) +{ + litest_slot_move(d, slot, x, y, true); +} + void litest_touch_move_to(struct litest_device *d, unsigned int slot, @@ -1036,6 +1394,98 @@ litest_touch_move_two_touches(struct litest_device *d, litest_touch_move(d, 1, x1 + dx, y1 + dy); } +void +litest_hover_start(struct litest_device *d, unsigned int slot, + double x, double y) +{ + litest_slot_start(d, slot, x, y, 0); +} + +void +litest_hover_end(struct litest_device *d, unsigned int slot) +{ + struct input_event *ev; + struct input_event up[] = { + { .type = EV_ABS, .code = ABS_MT_SLOT, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_MT_DISTANCE, .value = 1 }, + { .type = EV_ABS, .code = ABS_MT_TRACKING_ID, .value = -1 }, + { .type = EV_SYN, .code = SYN_REPORT, .value = 0 }, + { .type = -1, .code = -1 } + }; + + assert(d->ntouches_down > 0); + d->ntouches_down--; + + send_btntool(d); + + if (d->interface->touch_up) { + d->interface->touch_up(d, slot); + return; + } else if (d->interface->touch_up_events) { + ev = d->interface->touch_up_events; + } else + ev = up; + + while (ev && (int16_t)ev->type != -1 && (int16_t)ev->code != -1) { + int value = litest_auto_assign_value(d, ev, slot, 0, 0, 0); + litest_event(d, ev->type, ev->code, value); + ev++; + } +} + +void +litest_hover_move(struct litest_device *d, unsigned int slot, + double x, double y) +{ + litest_slot_move(d, slot, x, y, false); +} + +void +litest_hover_move_to(struct litest_device *d, + unsigned int slot, + double x_from, double y_from, + double x_to, double y_to, + int steps, int sleep_ms) +{ + for (int i = 0; i < steps - 1; i++) { + litest_hover_move(d, slot, + x_from + (x_to - x_from)/steps * i, + y_from + (y_to - y_from)/steps * i); + if (sleep_ms) { + libinput_dispatch(d->libinput); + msleep(sleep_ms); + libinput_dispatch(d->libinput); + } + } + litest_hover_move(d, slot, x_to, y_to); +} + +void +litest_hover_move_two_touches(struct litest_device *d, + double x0, double y0, + double x1, double y1, + double dx, double dy, + int steps, int sleep_ms) +{ + for (int i = 0; i < steps - 1; i++) { + litest_push_event_frame(d); + litest_hover_move(d, 0, x0 + dx / steps * i, + y0 + dy / steps * i); + litest_hover_move(d, 1, x1 + dx / steps * i, + y1 + dy / steps * i); + litest_pop_event_frame(d); + if (sleep_ms) { + libinput_dispatch(d->libinput); + msleep(sleep_ms); + libinput_dispatch(d->libinput); + } + } + litest_push_event_frame(d); + litest_hover_move(d, 0, x0 + dx, y0 + dy); + litest_hover_move(d, 1, x1 + dx, y1 + dy); + litest_pop_event_frame(d); +} + void litest_button_click(struct litest_device *d, unsigned int button, bool is_press) { @@ -1082,9 +1532,9 @@ int litest_scale(const struct litest_device *d, unsigned int axis, double val) { int min, max; - ck_assert_int_ge(val, 0); - ck_assert_int_le(val, 100); - ck_assert_int_le(axis, ABS_Y); + litest_assert_int_ge((int)val, 0); + litest_assert_int_le((int)val, 100); + litest_assert_int_le(axis, (unsigned int)ABS_Y); min = d->interface->min[axis]; max = d->interface->max[axis]; @@ -1274,7 +1724,7 @@ litest_assert_empty_queue(struct libinput *li) libinput_dispatch(li); } - ck_assert(empty_queue); + litest_assert(empty_queue); } struct libevdev_uinput * @@ -1300,7 +1750,7 @@ litest_create_uinput_device_from_description(const char *name, const char *devnode; dev = libevdev_new(); - ck_assert(dev != NULL); + litest_assert(dev != NULL); snprintf(buf, sizeof(buf), "litest %s", name); libevdev_set_name(dev, buf); @@ -1315,7 +1765,7 @@ litest_create_uinput_device_from_description(const char *name, while (abs && abs->value != -1) { rc = libevdev_enable_event_code(dev, EV_ABS, abs->value, abs); - ck_assert_int_eq(rc, 0); + litest_assert_int_eq(rc, 0); abs++; } @@ -1328,7 +1778,7 @@ litest_create_uinput_device_from_description(const char *name, rc = libevdev_enable_event_code(dev, type, code, type == EV_ABS ? &default_abs : NULL); } - ck_assert_int_eq(rc, 0); + litest_assert_int_eq(rc, 0); } rc = libevdev_uinput_create_from_device(dev, @@ -1338,29 +1788,28 @@ litest_create_uinput_device_from_description(const char *name, http://cgit.freedesktop.org/libevdev/commit/?id=debe9b030c8069cdf78307888ef3b65830b25122 */ if (rc == -EBADF) rc = -EACCES; - ck_assert_msg(rc == 0, "Failed to create uinput device: %s", strerror(-rc)); + litest_assert_msg(rc == 0, "Failed to create uinput device: %s", strerror(-rc)); libevdev_free(dev); + devnode = libevdev_uinput_get_devnode(uinput); + litest_assert_notnull(devnode); + fd = open(devnode, O_RDONLY); + litest_assert_int_gt(fd, -1); + rc = libevdev_new_from_fd(fd, &dev); + litest_assert_int_eq(rc, 0); + /* uinput does not yet support setting the resolution, so we set it * afterwards. This is of course racy as hell but the way we * _generally_ use this function by the time libinput uses the * device, we're finished here */ - - devnode = libevdev_uinput_get_devnode(uinput); - ck_assert_notnull(devnode); - fd = open(devnode, O_RDONLY); - ck_assert_int_gt(fd, -1); - rc = libevdev_new_from_fd(fd, &dev); - ck_assert_int_eq(rc, 0); - abs = abs_info; while (abs && abs->value != -1) { if (abs->resolution != 0) { rc = libevdev_kernel_set_abs_info(dev, abs->value, abs); - ck_assert_int_eq(rc, 0); + litest_assert_int_eq(rc, 0); } abs++; } @@ -1384,7 +1833,7 @@ litest_create_uinput_abs_device_v(const char *name, (code = va_arg(args, int)) != -1) { *event++ = type; *event++ = code; - ck_assert(event < &events[ARRAY_LENGTH(events) - 2]); + litest_assert(event < &events[ARRAY_LENGTH(events) - 2]); } *event++ = -1; @@ -1423,27 +1872,129 @@ litest_create_uinput_device(const char *name, struct input_id *id, ...) return uinput; } +struct libinput_event_pointer* +litest_is_button_event(struct libinput_event *event, + unsigned int button, + enum libinput_button_state state) +{ + struct libinput_event_pointer *ptrev; + enum libinput_event_type type = LIBINPUT_EVENT_POINTER_BUTTON; + + litest_assert(event != NULL); + litest_assert_int_eq(libinput_event_get_type(event), type); + ptrev = libinput_event_get_pointer_event(event); + litest_assert_int_eq(libinput_event_pointer_get_button(ptrev), + button); + litest_assert_int_eq(libinput_event_pointer_get_button_state(ptrev), + state); + + return ptrev; +} + +struct libinput_event_pointer * +litest_is_axis_event(struct libinput_event *event, + enum libinput_pointer_axis axis, + enum libinput_pointer_axis_source source) +{ + struct libinput_event_pointer *ptrev; + enum libinput_event_type type = LIBINPUT_EVENT_POINTER_AXIS; + + litest_assert(event != NULL); + litest_assert_int_eq(libinput_event_get_type(event), type); + ptrev = libinput_event_get_pointer_event(event); + litest_assert(libinput_event_pointer_has_axis(ptrev, axis)); + + if (source != 0) + litest_assert_int_eq(libinput_event_pointer_get_axis_source(ptrev), + source); + + return ptrev; +} + +struct libinput_event_pointer * +litest_is_motion_event(struct libinput_event *event) +{ + struct libinput_event_pointer *ptrev; + enum libinput_event_type type = LIBINPUT_EVENT_POINTER_MOTION; + double x, y, ux, uy; + + litest_assert(event != NULL); + litest_assert_int_eq(libinput_event_get_type(event), type); + ptrev = libinput_event_get_pointer_event(event); + + x = libinput_event_pointer_get_dx(ptrev); + y = libinput_event_pointer_get_dy(ptrev); + ux = libinput_event_pointer_get_dx_unaccelerated(ptrev); + uy = libinput_event_pointer_get_dy_unaccelerated(ptrev); + + /* No 0 delta motion events */ + litest_assert(x != 0.0 || y != 0.0 || + ux != 0.0 || uy != 0.0); + + return ptrev; +} + void litest_assert_button_event(struct libinput *li, unsigned int button, enum libinput_button_state state) { struct libinput_event *event; - struct libinput_event_pointer *ptrev; litest_wait_for_event(li); event = libinput_get_event(li); - ck_assert(event != NULL); - ck_assert_int_eq(libinput_event_get_type(event), - LIBINPUT_EVENT_POINTER_BUTTON); - ptrev = libinput_event_get_pointer_event(event); - ck_assert_int_eq(libinput_event_pointer_get_button(ptrev), - button); - ck_assert_int_eq(libinput_event_pointer_get_button_state(ptrev), - state); + litest_is_button_event(event, button, state); + libinput_event_destroy(event); } +struct libinput_event_touch * +litest_is_touch_event(struct libinput_event *event, + enum libinput_event_type type) +{ + struct libinput_event_touch *touch; + + litest_assert(event != NULL); + + if (type == 0) + type = libinput_event_get_type(event); + + switch (type) { + case LIBINPUT_EVENT_TOUCH_DOWN: + case LIBINPUT_EVENT_TOUCH_UP: + case LIBINPUT_EVENT_TOUCH_MOTION: + case LIBINPUT_EVENT_TOUCH_FRAME: + litest_assert_int_eq(libinput_event_get_type(event), type); + break; + default: + ck_abort_msg("%s: invalid touch type %d\n", __func__, type); + } + + touch = libinput_event_get_touch_event(event); + + return touch; +} + +struct libinput_event_keyboard * +litest_is_keyboard_event(struct libinput_event *event, + unsigned int key, + enum libinput_key_state state) +{ + struct libinput_event_keyboard *kevent; + enum libinput_event_type type = LIBINPUT_EVENT_KEYBOARD_KEY; + + litest_assert(event != NULL); + litest_assert_int_eq(libinput_event_get_type(event), type); + + kevent = libinput_event_get_keyboard_event(event); + litest_assert(kevent != NULL); + + litest_assert_int_eq(libinput_event_keyboard_get_key(kevent), key); + litest_assert_int_eq(libinput_event_keyboard_get_key_state(kevent), + state); + return kevent; +} + void litest_assert_tablet_button_event(struct libinput *li, unsigned int button, enum libinput_button_state state) @@ -1472,33 +2023,27 @@ litest_assert_scroll(struct libinput *li, { struct libinput_event *event, *next_event; struct libinput_event_pointer *ptrev; + int value; event = libinput_get_event(li); next_event = libinput_get_event(li); - ck_assert(next_event != NULL); /* At least 1 scroll + stop scroll */ + litest_assert(next_event != NULL); /* At least 1 scroll + stop scroll */ while (event) { - ck_assert_int_eq(libinput_event_get_type(event), - LIBINPUT_EVENT_POINTER_AXIS); - ptrev = libinput_event_get_pointer_event(event); - ck_assert(ptrev != NULL); + ptrev = litest_is_axis_event(event, axis, 0); if (next_event) { + value = libinput_event_pointer_get_axis_value(ptrev, + axis); /* Normal scroll event, check dir */ if (minimum_movement > 0) { - ck_assert_int_ge( - libinput_event_pointer_get_axis_value(ptrev, - axis), - minimum_movement); + litest_assert_int_ge(value, minimum_movement); } else { - ck_assert_int_le( - libinput_event_pointer_get_axis_value(ptrev, - axis), - minimum_movement); + litest_assert_int_le(value, minimum_movement); } } else { /* Last scroll event, must be 0 */ - ck_assert_int_eq( + litest_assert_int_eq( libinput_event_pointer_get_axis_value(ptrev, axis), 0); } @@ -1518,11 +2063,11 @@ litest_assert_only_typed_events(struct libinput *li, libinput_dispatch(li); event = libinput_get_event(li); - ck_assert_notnull(event); + litest_assert_notnull(event); while (event) { - ck_assert_int_eq(libinput_event_get_type(event), - type); + litest_assert_int_eq(libinput_event_get_type(event), + type); libinput_event_destroy(event); libinput_dispatch(li); event = libinput_get_event(li); @@ -1535,6 +2080,12 @@ litest_timeout_tap(void) msleep(200); } +void +litest_timeout_tapndrag(void) +{ + msleep(520); +} + void litest_timeout_softbuttons(void) { @@ -1559,6 +2110,12 @@ litest_timeout_edgescroll(void) msleep(300); } +void +litest_timeout_middlebutton(void) +{ + msleep(70); +} + void litest_push_event_frame(struct litest_device *dev) { @@ -1583,11 +2140,11 @@ send_abs_xy(struct litest_device *d, double x, double y) e.type = EV_ABS; e.code = ABS_X; e.value = LITEST_AUTO_ASSIGN; - val = litest_auto_assign_value(d, &e, 0, x, y); + val = litest_auto_assign_value(d, &e, 0, x, y, true); litest_event(d, EV_ABS, ABS_X, val); e.code = ABS_Y; - val = litest_auto_assign_value(d, &e, 0, x, y); + val = litest_auto_assign_value(d, &e, 0, x, y, true); litest_event(d, EV_ABS, ABS_Y, val); } @@ -1600,12 +2157,12 @@ send_abs_mt_xy(struct litest_device *d, double x, double y) e.type = EV_ABS; e.code = ABS_MT_POSITION_X; e.value = LITEST_AUTO_ASSIGN; - val = litest_auto_assign_value(d, &e, 0, x, y); + val = litest_auto_assign_value(d, &e, 0, x, y, true); litest_event(d, EV_ABS, ABS_MT_POSITION_X, val); e.code = ABS_MT_POSITION_Y; e.value = LITEST_AUTO_ASSIGN; - val = litest_auto_assign_value(d, &e, 0, x, y); + val = litest_auto_assign_value(d, &e, 0, x, y, true); litest_event(d, EV_ABS, ABS_MT_POSITION_Y, val); } @@ -1714,3 +2271,67 @@ litest_semi_mt_touch_up(struct litest_device *d, litest_event(d, EV_SYN, SYN_REPORT, 0); } + +static inline int +litest_parse_argv(int argc, char **argv) +{ + enum { + OPT_FILTER_TEST, + OPT_FILTER_DEVICE, + OPT_FILTER_GROUP, + OPT_LIST, + OPT_VERBOSE, + }; + static const struct option opts[] = { + { "filter-test", 1, 0, OPT_FILTER_TEST }, + { "filter-device", 1, 0, OPT_FILTER_DEVICE }, + { "filter-group", 1, 0, OPT_FILTER_GROUP }, + { "list", 0, 0, OPT_LIST }, + { "verbose", 0, 0, OPT_VERBOSE }, + { 0, 0, 0, 0} + }; + + while(1) { + int c; + int option_index = 0; + + c = getopt_long(argc, argv, "", opts, &option_index); + if (c == -1) + break; + switch(c) { + case OPT_FILTER_TEST: + filter_test = optarg; + break; + case OPT_FILTER_DEVICE: + filter_device = optarg; + break; + case OPT_FILTER_GROUP: + filter_group = optarg; + break; + case OPT_LIST: + litest_list_tests(&all_tests); + exit(0); + case OPT_VERBOSE: + verbose = 1; + break; + default: + fprintf(stderr, "usage: %s [--list]\n", argv[0]); + return 1; + } + } + + return 0; +} + +#ifndef LITEST_NO_MAIN +int +main(int argc, char **argv) +{ + if (litest_parse_argv(argc, argv) != 0) + return EXIT_FAILURE; + + litest_setup_tests(); + + return litest_run(argc, argv); +} +#endif diff --git a/test/litest.h b/test/litest.h index 0214a7d8..9efacde8 100644 --- a/test/litest.h +++ b/test/litest.h @@ -32,6 +32,83 @@ #include #include #include +#include + +#define litest_assert(cond) \ + do { \ + if (!(cond)) \ + litest_fail_condition(__FILE__, __LINE__, __func__, \ + #cond, NULL); \ + } while(0) + +#define litest_assert_msg(cond, ...) \ + do { \ + if (!(cond)) \ + litest_fail_condition(__FILE__, __LINE__, __func__, \ + #cond, __VA_ARGS__); \ + } while(0) + +#define litest_abort_msg(...) \ + litest_fail_condition(__FILE__, __LINE__, __func__, \ + "aborting", __VA_ARGS__); \ + +#define litest_assert_notnull(cond) \ + do { \ + if ((cond) == NULL) \ + litest_fail_condition(__FILE__, __LINE__, __func__, \ + #cond, " expected to be not NULL\n"); \ + } while(0) + +#define litest_assert_comparison_int_(a_, op_, b_) \ + do { \ + __typeof__(a_) _a = a_; \ + __typeof__(b_) _b = b_; \ + if (trunc(_a) != _a || trunc(_b) != _b) \ + litest_abort_msg("litest_assert_int_* used for non-integer value\n"); \ + if (!((_a) op_ (_b))) \ + litest_fail_comparison_int(__FILE__, __LINE__, __func__,\ + #op_, _a, _b, \ + #a_, #b_); \ + } while(0) + +#define litest_assert_int_eq(a_, b_) \ + litest_assert_comparison_int_(a_, ==, b_) + +#define litest_assert_int_ne(a_, b_) \ + litest_assert_comparison_int_(a_, !=, b_) + +#define litest_assert_int_lt(a_, b_) \ + litest_assert_comparison_int_(a_, <, b_) + +#define litest_assert_int_le(a_, b_) \ + litest_assert_comparison_int_(a_, <=, b_) + +#define litest_assert_int_ge(a_, b_) \ + litest_assert_comparison_int_(a_, >=, b_) + +#define litest_assert_int_gt(a_, b_) \ + litest_assert_comparison_int_(a_, >, b_) + +#define litest_assert_comparison_ptr_(a_, op_, b_) \ + do { \ + __typeof__(a_) _a = a_; \ + __typeof__(b_) _b = b_; \ + if (!((_a) op_ (_b))) \ + litest_fail_comparison_ptr(__FILE__, __LINE__, __func__,\ + #a_ " " #op_ " " #b_); \ + } while(0) + +#define litest_assert_ptr_eq(a_, b_) \ + litest_assert_comparison_ptr_(a_, ==, b_) + +#define litest_assert_ptr_ne(a_, b_) \ + litest_assert_comparison_ptr_(a_, !=, b_) + +#define litest_assert_ptr_null(a_) \ + litest_assert_comparison_ptr_(a_, ==, NULL) + +#define litest_assert_ptr_notnull(a_) \ + litest_assert_comparison_ptr_(a_, !=, NULL) enum litest_device_type { LITEST_NO_DEVICE = -1, @@ -53,10 +130,15 @@ enum litest_device_type { LITEST_SYNAPTICS_TRACKPOINT_BUTTONS = -17, LITEST_PROTOCOL_A_SCREEN = -18, LITEST_WACOM_FINGER = -19, - LITEST_WACOM_BAMBOO = -20, - LITEST_WACOM_CINTIQ = -21, - LITEST_WACOM_INTUOS = -22, - LITEST_WACOM_ISDV4 = -23, + LITEST_KEYBOARD_BLACKWIDOW = -20, + LITEST_WHEEL_ONLY = -21, + LITEST_MOUSE_ROCCAT = -22, + LITEST_LOGITECH_TRACKBALL = -23, + LITEST_ATMEL_HOVER = -24, + LITEST_WACOM_BAMBOO = -25, + LITEST_WACOM_CINTIQ = -26, + LITEST_WACOM_INTUOS = -27, + LITEST_WACOM_ISDV4 = -28, }; enum litest_device_feature { @@ -77,9 +159,10 @@ enum litest_device_feature { LITEST_FAKE_MT = 1 << 12, LITEST_ABSOLUTE = 1 << 13, LITEST_PROTOCOL_A = 1 << 14, - LITEST_TABLET = 1 << 15, - LITEST_DISTANCE = 1 << 16, - LITEST_TOOL_SERIAL = 1 << 17, + LITEST_HOVER = 1 << 15, + LITEST_TABLET = 1 << 16, + LITEST_DISTANCE = 1 << 17, + LITEST_TOOL_SERIAL = 1 << 18, }; struct litest_device { @@ -103,20 +186,81 @@ struct axis_replacement { int32_t value; }; +/* A loop range, resolves to: + for (i = lower; i < upper; i++) + */ +struct range { + int lower; /* inclusive */ + int upper; /* exclusive */ +}; + struct libinput *litest_create_context(void); void litest_disable_log_handler(struct libinput *libinput); void litest_restore_log_handler(struct libinput *libinput); -void litest_add(const char *name, void *func, - enum litest_device_feature required_feature, - enum litest_device_feature excluded_feature); void -litest_add_for_device(const char *name, - void *func, - enum litest_device_type type); -void litest_add_no_device(const char *name, void *func); +litest_fail_condition(const char *file, + int line, + const char *func, + const char *condition, + const char *message, + ...); +void +litest_fail_comparison_int(const char *file, + int line, + const char *func, + const char *operator, + int a, + int b, + const char *astr, + const char *bstr); +void +litest_fail_comparison_ptr(const char *file, + int line, + const char *func, + const char *comparison); -int litest_run(int argc, char **argv); +#define litest_add(name_, func_, ...) \ + _litest_add(name_, #func_, func_, __VA_ARGS__) +#define litest_add_ranged(name_, func_, ...) \ + _litest_add_ranged(name_, #func_, func_, __VA_ARGS__) +#define litest_add_for_device(name_, func_, ...) \ + _litest_add_for_device(name_, #func_, func_, __VA_ARGS__) +#define litest_add_ranged_for_device(name_, func_, ...) \ + _litest_add_ranged_for_device(name_, #func_, func_, __VA_ARGS__) +#define litest_add_no_device(name_, func_) \ + _litest_add_no_device(name_, #func_, func_) +#define litest_add_ranged_no_device(name_, func_, ...) \ + _litest_add_ranged_no_device(name_, #func_, func_, __VA_ARGS__) +void _litest_add(const char *name, + const char *funcname, + void *func, + enum litest_device_feature required_feature, + enum litest_device_feature excluded_feature); +void _litest_add_ranged(const char *name, + const char *funcname, + void *func, + enum litest_device_feature required, + enum litest_device_feature excluded, + const struct range *range); +void _litest_add_for_device(const char *name, + const char *funcname, + void *func, + enum litest_device_type type); +void _litest_add_ranged_for_device(const char *name, + const char *funcname, + void *func, + enum litest_device_type type, + const struct range *range); +void _litest_add_no_device(const char *name, + const char *funcname, + void *func); +void _litest_add_ranged_no_device(const char *name, + const char *funcname, + void *func, + const struct range *range); + +extern void litest_setup_tests(void); struct litest_device * litest_create_device(enum litest_device_type which); struct litest_device * litest_add_device(struct libinput *libinput, enum litest_device_type which); @@ -149,7 +293,8 @@ void litest_event(struct litest_device *t, int value); int litest_auto_assign_value(struct litest_device *d, const struct input_event *ev, - int slot, double x, double y); + int slot, double x, double y, + bool touching); void litest_touch_up(struct litest_device *d, unsigned int slot); void litest_touch_move(struct litest_device *d, unsigned int slot, @@ -178,6 +323,25 @@ void litest_tablet_motion(struct litest_device *d, int x, int y, struct axis_replacement *axes); +void litest_hover_start(struct litest_device *d, + unsigned int slot, + double x, + double y); +void litest_hover_end(struct litest_device *d, unsigned int slot); +void litest_hover_move(struct litest_device *d, + unsigned int slot, + double x, + double y); +void litest_hover_move_to(struct litest_device *d, + unsigned int slot, + double x_from, double y_from, + double x_to, double y_to, + int steps, int sleep_ms); +void litest_hover_move_two_touches(struct litest_device *d, + double x0, double y0, + double x1, double y1, + double dx, double dy, + int steps, int sleep_ms); void litest_button_click(struct litest_device *d, unsigned int button, bool is_press); @@ -191,6 +355,23 @@ void litest_wait_for_event(struct libinput *li); void litest_wait_for_event_of_type(struct libinput *li, ...); void litest_drain_events(struct libinput *li); void litest_assert_empty_queue(struct libinput *li); +struct libinput_event_pointer * litest_is_button_event( + struct libinput_event *event, + unsigned int button, + enum libinput_button_state state); +struct libinput_event_pointer * litest_is_axis_event( + struct libinput_event *event, + enum libinput_pointer_axis axis, + enum libinput_pointer_axis_source source); +struct libinput_event_pointer * litest_is_motion_event( + struct libinput_event *event); +struct libinput_event_touch * litest_is_touch_event( + struct libinput_event *event, + enum libinput_event_type type); +struct libinput_event_keyboard * litest_is_keyboard_event( + struct libinput_event *event, + unsigned int key, + enum libinput_key_state state); void litest_assert_button_event(struct libinput *li, unsigned int button, enum libinput_button_state state); @@ -229,10 +410,12 @@ struct libevdev_uinput * litest_create_uinput_abs_device(const char *name, ck_assert_int_ge((int)((a_) * 256), (int)((b_) * 256)) void litest_timeout_tap(void); +void litest_timeout_tapndrag(void); void litest_timeout_softbuttons(void); void litest_timeout_buttonscroll(void); void litest_timeout_edgescroll(void); void litest_timeout_finger_switch(void); +void litest_timeout_middlebutton(void); void litest_push_event_frame(struct litest_device *dev); void litest_pop_event_frame(struct litest_device *dev); diff --git a/test/log.c b/test/log.c index a56af151..2ab3d4d8 100644 --- a/test/log.c +++ b/test/log.c @@ -26,7 +26,6 @@ #include #include #include -#include #include #include "litest.h" @@ -58,8 +57,8 @@ simple_log_handler(struct libinput *libinput, { log_handler_called++; if (log_handler_context) - ck_assert(libinput == log_handler_context); - ck_assert(format != NULL); + litest_assert_ptr_eq(libinput, log_handler_context); + litest_assert_notnull(format); } START_TEST(log_default_priority) @@ -140,11 +139,11 @@ START_TEST(log_priority) } END_TEST -int main (int argc, char **argv) { +void +litest_setup_tests(void) +{ litest_add_no_device("log:defaults", log_default_priority); litest_add_no_device("log:logging", log_handler_invoked); litest_add_no_device("log:logging", log_handler_NULL); litest_add_no_device("log:logging", log_priority); - - return litest_run(argc, argv); } diff --git a/test/misc.c b/test/misc.c index 0b6532ca..39683c80 100644 --- a/test/misc.c +++ b/test/misc.c @@ -65,7 +65,7 @@ create_simple_test_device(const char *name, ...) }; evdev = libevdev_new(); - ck_assert(evdev != NULL); + litest_assert_notnull(evdev); libevdev_set_name(evdev, name); va_start(args, name); @@ -83,7 +83,7 @@ create_simple_test_device(const char *name, ...) rc = libevdev_uinput_create_from_device(evdev, LIBEVDEV_UINPUT_OPEN_MANAGED, &uinput); - ck_assert_int_eq(rc, 0); + litest_assert_int_eq(rc, 0); libevdev_free(evdev); return uinput; @@ -595,7 +595,35 @@ START_TEST(wheel_click_parser) } END_TEST -int main (int argc, char **argv) { +struct parser_test_float { + char *tag; + double expected_value; +}; + +START_TEST(trackpoint_accel_parser) +{ + struct parser_test_float tests[] = { + { "0.5", 0.5 }, + { "1.0", 1.0 }, + { "2.0", 2.0 }, + { "fail1.0", 0.0 }, + { "1.0fail", 0.0 }, + { "0,5", 0.0 }, + { NULL, 0.0 } + }; + int i; + double accel; + + for (i = 0; tests[i].tag != NULL; i++) { + accel = parse_trackpoint_accel_property(tests[i].tag); + ck_assert(accel == tests[i].expected_value); + } +} +END_TEST + +void +litest_setup_tests(void) +{ litest_add_no_device("events:conversion", event_conversion_device_notify); litest_add_for_device("events:conversion", event_conversion_pointer, LITEST_MOUSE); litest_add_for_device("events:conversion", event_conversion_pointer, LITEST_MOUSE); @@ -611,6 +639,5 @@ int main (int argc, char **argv) { litest_add_no_device("misc:ratelimit", ratelimit_helpers); litest_add_no_device("misc:dpi parser", dpi_parser); litest_add_no_device("misc:wheel click parser", wheel_click_parser); - - return litest_run(argc, argv); + litest_add_no_device("misc:trackpoint accel parser", trackpoint_accel_parser); } diff --git a/test/path.c b/test/path.c index 64c5d7f4..c28c6ff4 100644 --- a/test/path.c +++ b/test/path.c @@ -26,7 +26,6 @@ #include #include #include -#include #include #include "litest.h" @@ -875,8 +874,8 @@ START_TEST(path_seat_recycle) } END_TEST -int -main(int argc, char **argv) +void +litest_setup_tests(void) { litest_add_no_device("path:create", path_create_NULL); litest_add_no_device("path:create", path_create_invalid); @@ -897,6 +896,4 @@ main(int argc, char **argv) litest_add_for_device("path:device events", path_remove_device, LITEST_SYNAPTICS_CLICKPAD); litest_add_for_device("path:device events", path_double_remove_device, LITEST_SYNAPTICS_CLICKPAD); litest_add_no_device("path:seat", path_seat_recycle); - - return litest_run(argc, argv); } diff --git a/test/pointer.c b/test/pointer.c index 745af638..2918754a 100644 --- a/test/pointer.c +++ b/test/pointer.c @@ -42,12 +42,7 @@ get_accelerated_motion_event(struct libinput *li) while (1) { event = libinput_get_event(li); - ck_assert_notnull(event); - ck_assert_int_eq(libinput_event_get_type(event), - LIBINPUT_EVENT_POINTER_MOTION); - - ptrev = libinput_event_get_pointer_event(event); - ck_assert_notnull(ptrev); + ptrev = litest_is_motion_event(event); if (fabs(libinput_event_pointer_get_dx(ptrev)) < DBL_MIN && fabs(libinput_event_pointer_get_dy(ptrev)) < DBL_MIN) { @@ -58,7 +53,7 @@ get_accelerated_motion_event(struct libinput *li) return ptrev; } - ck_abort_msg("No accelerated pointer motion event found"); + litest_abort_msg("No accelerated pointer motion event found"); return NULL; } @@ -95,17 +90,31 @@ test_relative_event(struct litest_device *dev, int dx, int dy) actual_dir = atan2(ev_dx, ev_dy); /* Check the length of the motion vector (tolerate 1.0 indifference). */ - ck_assert(fabs(expected_length) >= actual_length); + litest_assert(fabs(expected_length) >= actual_length); /* Check the direction of the motion vector (tolerate 2π/4 radians * indifference). */ - ck_assert(fabs(expected_dir - actual_dir) < M_PI_2); + litest_assert(fabs(expected_dir - actual_dir) < M_PI_2); libinput_event_destroy(libinput_event_pointer_get_base_event(ptrev)); litest_drain_events(dev->libinput); } +static void +disable_button_scrolling(struct litest_device *device) +{ + struct libinput_device *dev = device->libinput_device; + enum libinput_config_status status, + expected; + + status = libinput_device_config_scroll_set_method(dev, + LIBINPUT_CONFIG_SCROLL_NO_SCROLL); + + expected = LIBINPUT_CONFIG_STATUS_SUCCESS; + litest_assert_int_eq(status, expected); +} + START_TEST(pointer_motion_relative) { struct litest_device *dev = litest_current_device(); @@ -131,21 +140,22 @@ test_absolute_event(struct litest_device *dev, double x, double y) struct libinput_event *event; struct libinput_event_pointer *ptrev; double ex, ey; + enum libinput_event_type type = LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE; litest_touch_down(dev, 0, x, y); libinput_dispatch(li); event = libinput_get_event(li); - ck_assert_int_eq(libinput_event_get_type(event), - LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE); + litest_assert_notnull(event); + litest_assert_int_eq(libinput_event_get_type(event), type); ptrev = libinput_event_get_pointer_event(event); - ck_assert(ptrev != NULL); + litest_assert(ptrev != NULL); ex = libinput_event_pointer_get_absolute_x_transformed(ptrev, 100); ey = libinput_event_pointer_get_absolute_y_transformed(ptrev, 100); - ck_assert_int_eq(ex + 0.5, x); - ck_assert_int_eq(ey + 0.5, y); + litest_assert_int_eq((int)(ex + 0.5), (int)x); + litest_assert_int_eq((int)(ey + 0.5), (int)y); libinput_event_destroy(event); } @@ -162,6 +172,61 @@ START_TEST(pointer_motion_absolute) } END_TEST +START_TEST(pointer_absolute_initial_state) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *libinput1, *libinput2; + struct libinput_event *ev1, *ev2; + struct libinput_event_pointer *p1, *p2; + int axis = _i; /* looped test */ + + dev = litest_current_device(); + libinput1 = dev->libinput; + litest_touch_down(dev, 0, 40, 60); + litest_touch_up(dev, 0); + + /* device is now on some x/y value */ + litest_drain_events(libinput1); + + libinput2 = litest_create_context(); + libinput_path_add_device(libinput2, + libevdev_uinput_get_devnode(dev->uinput)); + litest_drain_events(libinput2); + + if (axis == ABS_X) + litest_touch_down(dev, 0, 40, 70); + else + litest_touch_down(dev, 0, 70, 60); + litest_touch_up(dev, 0); + + litest_wait_for_event(libinput1); + litest_wait_for_event(libinput2); + + while (libinput_next_event_type(libinput1)) { + ev1 = libinput_get_event(libinput1); + ev2 = libinput_get_event(libinput2); + + ck_assert_int_eq(libinput_event_get_type(ev1), + LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE); + ck_assert_int_eq(libinput_event_get_type(ev1), + libinput_event_get_type(ev2)); + + p1 = libinput_event_get_pointer_event(ev1); + p2 = libinput_event_get_pointer_event(ev2); + + ck_assert_int_eq(libinput_event_pointer_get_absolute_x(p1), + libinput_event_pointer_get_absolute_x(p2)); + ck_assert_int_eq(libinput_event_pointer_get_absolute_y(p1), + libinput_event_pointer_get_absolute_y(p2)); + + libinput_event_destroy(ev1); + libinput_event_destroy(ev2); + } + + libinput_unref(libinput2); +} +END_TEST + static void test_unaccel_event(struct litest_device *dev, int dx, int dy) { @@ -177,18 +242,13 @@ test_unaccel_event(struct litest_device *dev, int dx, int dy) libinput_dispatch(li); event = libinput_get_event(li); - ck_assert_notnull(event); - ck_assert_int_eq(libinput_event_get_type(event), - LIBINPUT_EVENT_POINTER_MOTION); - - ptrev = libinput_event_get_pointer_event(event); - ck_assert(ptrev != NULL); + ptrev = litest_is_motion_event(event); ev_dx = libinput_event_pointer_get_dx_unaccelerated(ptrev); ev_dy = libinput_event_pointer_get_dy_unaccelerated(ptrev); - ck_assert_int_eq(dx, ev_dx); - ck_assert_int_eq(dy, ev_dy); + litest_assert_int_eq(dx, ev_dx); + litest_assert_int_eq(dy, ev_dy); libinput_event_destroy(event); @@ -230,6 +290,8 @@ START_TEST(pointer_button) { struct litest_device *dev = litest_current_device(); + disable_button_scrolling(dev); + litest_drain_events(dev->libinput); test_button_event(dev, BTN_LEFT, 1); @@ -245,8 +307,7 @@ START_TEST(pointer_button) } /* Skip middle button test on trackpoints (used for scrolling) */ - if (!libevdev_has_property(dev->evdev, INPUT_PROP_POINTING_STICK) && - libevdev_has_event_code(dev->evdev, EV_KEY, BTN_MIDDLE)) { + if (libevdev_has_event_code(dev->evdev, EV_KEY, BTN_MIDDLE)) { test_button_event(dev, BTN_MIDDLE, 1); test_button_event(dev, BTN_MIDDLE, 0); } @@ -369,24 +430,18 @@ test_wheel_event(struct litest_device *dev, int which, int amount) libinput_dispatch(li); - event = libinput_get_event(li); - ck_assert(event != NULL); - ck_assert_int_eq(libinput_event_get_type(event), - LIBINPUT_EVENT_POINTER_AXIS); - - ptrev = libinput_event_get_pointer_event(event); - ck_assert(ptrev != NULL); - axis = (which == REL_WHEEL) ? LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL : LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL; + event = libinput_get_event(li); + ptrev = litest_is_axis_event(event, + axis, + LIBINPUT_POINTER_AXIS_SOURCE_WHEEL); - ck_assert_int_eq(libinput_event_pointer_get_axis_value(ptrev, axis), + litest_assert_int_eq(libinput_event_pointer_get_axis_value(ptrev, axis), expected); - ck_assert_int_eq(libinput_event_pointer_get_axis_source(ptrev), - LIBINPUT_POINTER_AXIS_SOURCE_WHEEL); - ck_assert_int_eq(libinput_event_pointer_get_axis_value_discrete(ptrev, axis), - discrete); + litest_assert_int_eq(libinput_event_pointer_get_axis_value_discrete(ptrev, axis), + discrete); libinput_event_destroy(event); } @@ -396,11 +451,17 @@ START_TEST(pointer_scroll_wheel) litest_drain_events(dev->libinput); - test_wheel_event(dev, REL_WHEEL, -1); - test_wheel_event(dev, REL_WHEEL, 1); + /* make sure we hit at least one of the below two conditions */ + ck_assert(libevdev_has_event_code(dev->evdev, EV_REL, REL_WHEEL) || + libevdev_has_event_code(dev->evdev, EV_REL, REL_HWHEEL)); - test_wheel_event(dev, REL_WHEEL, -5); - test_wheel_event(dev, REL_WHEEL, 6); + if (libevdev_has_event_code(dev->evdev, EV_REL, REL_WHEEL)) { + test_wheel_event(dev, REL_WHEEL, -1); + test_wheel_event(dev, REL_WHEEL, 1); + + test_wheel_event(dev, REL_WHEEL, -5); + test_wheel_event(dev, REL_WHEEL, 6); + } if (libevdev_has_event_code(dev->evdev, EV_REL, REL_HWHEEL)) { test_wheel_event(dev, REL_HWHEEL, -1); @@ -446,11 +507,17 @@ START_TEST(pointer_scroll_natural_wheel) libinput_device_config_scroll_set_natural_scroll_enabled(device, 1); - test_wheel_event(dev, REL_WHEEL, -1); - test_wheel_event(dev, REL_WHEEL, 1); + /* make sure we hit at least one of the below two conditions */ + ck_assert(libevdev_has_event_code(dev->evdev, EV_REL, REL_WHEEL) || + libevdev_has_event_code(dev->evdev, EV_REL, REL_HWHEEL)); - test_wheel_event(dev, REL_WHEEL, -5); - test_wheel_event(dev, REL_WHEEL, 6); + if (libevdev_has_event_code(dev->evdev, EV_REL, REL_WHEEL)) { + test_wheel_event(dev, REL_WHEEL, -1); + test_wheel_event(dev, REL_WHEEL, 1); + + test_wheel_event(dev, REL_WHEEL, -5); + test_wheel_event(dev, REL_WHEEL, 6); + } if (libevdev_has_event_code(dev->evdev, EV_REL, REL_HWHEEL)) { test_wheel_event(dev, REL_HWHEEL, -1); @@ -732,6 +799,26 @@ START_TEST(pointer_scroll_button) } END_TEST +START_TEST(pointer_scroll_nowheel_defaults) +{ + struct litest_device *dev = litest_current_device(); + struct libinput_device *device = dev->libinput_device; + enum libinput_config_scroll_method method; + uint32_t button; + + method = libinput_device_config_scroll_get_method(device); + ck_assert_int_eq(method, LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN); + + method = libinput_device_config_scroll_get_default_method(device); + ck_assert_int_eq(method, LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN); + + button = libinput_device_config_scroll_get_button(device); + ck_assert_int_eq(button, BTN_MIDDLE); + button = libinput_device_config_scroll_get_default_button(device); + ck_assert_int_eq(button, BTN_MIDDLE); +} +END_TEST + START_TEST(pointer_accel_defaults) { struct litest_device *dev = litest_current_device(); @@ -812,6 +899,17 @@ START_TEST(pointer_accel_defaults_absolute) } END_TEST +START_TEST(pointer_accel_defaults_absolute_relative) +{ + struct litest_device *dev = litest_current_device(); + struct libinput_device *device = dev->libinput_device; + + ck_assert(libinput_device_config_accel_is_available(device)); + ck_assert(libinput_device_config_accel_get_default_speed(device) == 0.0); + ck_assert(libinput_device_config_accel_get_speed(device) == 0.0); +} +END_TEST + START_TEST(pointer_accel_direction_change) { struct litest_device *dev = litest_current_device(); @@ -854,19 +952,389 @@ START_TEST(pointer_accel_direction_change) } END_TEST -int main (int argc, char **argv) { +START_TEST(middlebutton) +{ + struct litest_device *device = litest_current_device(); + struct libinput *li = device->libinput; + enum libinput_config_status status; + unsigned int i; + const int btn[][4] = { + { BTN_LEFT, BTN_RIGHT, BTN_LEFT, BTN_RIGHT }, + { BTN_LEFT, BTN_RIGHT, BTN_RIGHT, BTN_LEFT }, + { BTN_RIGHT, BTN_LEFT, BTN_LEFT, BTN_RIGHT }, + { BTN_RIGHT, BTN_LEFT, BTN_RIGHT, BTN_LEFT }, + }; + + disable_button_scrolling(device); + + status = libinput_device_config_middle_emulation_set_enabled( + device->libinput_device, + LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED); + if (status == LIBINPUT_CONFIG_STATUS_UNSUPPORTED) + return; + + litest_drain_events(li); + + for (i = 0; i < ARRAY_LENGTH(btn); i++) { + litest_button_click(device, btn[i][0], true); + litest_button_click(device, btn[i][1], true); + + litest_assert_button_event(li, + BTN_MIDDLE, + LIBINPUT_BUTTON_STATE_PRESSED); + litest_assert_empty_queue(li); + + litest_button_click(device, btn[i][2], false); + litest_button_click(device, btn[i][3], false); + litest_assert_button_event(li, + BTN_MIDDLE, + LIBINPUT_BUTTON_STATE_RELEASED); + litest_assert_empty_queue(li); + } +} +END_TEST + +START_TEST(middlebutton_timeout) +{ + struct litest_device *device = litest_current_device(); + struct libinput *li = device->libinput; + enum libinput_config_status status; + unsigned int button; + + disable_button_scrolling(device); + + status = libinput_device_config_middle_emulation_set_enabled( + device->libinput_device, + LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED); + if (status == LIBINPUT_CONFIG_STATUS_UNSUPPORTED) + return; + + for (button = BTN_LEFT; button <= BTN_RIGHT; button++) { + litest_drain_events(li); + litest_button_click(device, button, true); + litest_assert_empty_queue(li); + litest_timeout_middlebutton(); + + litest_assert_button_event(li, + button, + LIBINPUT_BUTTON_STATE_PRESSED); + + litest_button_click(device, button, false); + litest_assert_button_event(li, + button, + LIBINPUT_BUTTON_STATE_RELEASED); + litest_assert_empty_queue(li); + } +} +END_TEST + +START_TEST(middlebutton_doubleclick) +{ + struct litest_device *device = litest_current_device(); + struct libinput *li = device->libinput; + enum libinput_config_status status; + unsigned int i; + const int btn[][4] = { + { BTN_LEFT, BTN_RIGHT, BTN_LEFT, BTN_RIGHT }, + { BTN_LEFT, BTN_RIGHT, BTN_RIGHT, BTN_LEFT }, + { BTN_RIGHT, BTN_LEFT, BTN_LEFT, BTN_RIGHT }, + { BTN_RIGHT, BTN_LEFT, BTN_RIGHT, BTN_LEFT }, + }; + + disable_button_scrolling(device); + + status = libinput_device_config_middle_emulation_set_enabled( + device->libinput_device, + LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED); + if (status == LIBINPUT_CONFIG_STATUS_UNSUPPORTED) + return; + + litest_drain_events(li); + + for (i = 0; i < ARRAY_LENGTH(btn); i++) { + litest_button_click(device, btn[i][0], true); + litest_button_click(device, btn[i][1], true); + litest_assert_button_event(li, + BTN_MIDDLE, + LIBINPUT_BUTTON_STATE_PRESSED); + litest_assert_empty_queue(li); + + litest_button_click(device, btn[i][2], false); + litest_button_click(device, btn[i][2], true); + litest_assert_button_event(li, + BTN_MIDDLE, + LIBINPUT_BUTTON_STATE_RELEASED); + litest_assert_button_event(li, + BTN_MIDDLE, + LIBINPUT_BUTTON_STATE_PRESSED); + litest_button_click(device, btn[i][3], false); + + litest_assert_button_event(li, + BTN_MIDDLE, + LIBINPUT_BUTTON_STATE_RELEASED); + litest_assert_empty_queue(li); + } +} +END_TEST + +START_TEST(middlebutton_middleclick) +{ + struct litest_device *device = litest_current_device(); + struct libinput *li = device->libinput; + enum libinput_config_status status; + unsigned int button; + + disable_button_scrolling(device); + + if (!libevdev_has_event_code(device->evdev, EV_KEY, BTN_MIDDLE)) + return; + + status = libinput_device_config_middle_emulation_set_enabled( + device->libinput_device, + LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED); + if (status == LIBINPUT_CONFIG_STATUS_UNSUPPORTED) + return; + + /* one button down, then middle -> release buttons */ + for (button = BTN_LEFT; button <= BTN_RIGHT; button++) { + /* release button before middle */ + litest_drain_events(li); + litest_button_click(device, button, true); + litest_button_click(device, BTN_MIDDLE, true); + litest_assert_button_event(li, + button, + LIBINPUT_BUTTON_STATE_PRESSED); + litest_assert_button_event(li, + BTN_MIDDLE, + LIBINPUT_BUTTON_STATE_PRESSED); + litest_assert_empty_queue(li); + litest_button_click(device, button, false); + litest_assert_button_event(li, + button, + LIBINPUT_BUTTON_STATE_RELEASED); + litest_button_click(device, BTN_MIDDLE, false); + litest_assert_button_event(li, + BTN_MIDDLE, + LIBINPUT_BUTTON_STATE_RELEASED); + litest_assert_empty_queue(li); + + /* release middle before button */ + litest_button_click(device, button, true); + litest_button_click(device, BTN_MIDDLE, true); + litest_assert_button_event(li, + button, + LIBINPUT_BUTTON_STATE_PRESSED); + litest_assert_button_event(li, + BTN_MIDDLE, + LIBINPUT_BUTTON_STATE_PRESSED); + litest_assert_empty_queue(li); + litest_button_click(device, BTN_MIDDLE, false); + litest_assert_button_event(li, + BTN_MIDDLE, + LIBINPUT_BUTTON_STATE_RELEASED); + litest_button_click(device, button, false); + litest_assert_button_event(li, + button, + LIBINPUT_BUTTON_STATE_RELEASED); + litest_assert_empty_queue(li); + } +} +END_TEST + +START_TEST(middlebutton_middleclick_during) +{ + struct litest_device *device = litest_current_device(); + struct libinput *li = device->libinput; + enum libinput_config_status status; + unsigned int button; + + disable_button_scrolling(device); + + if (!libevdev_has_event_code(device->evdev, EV_KEY, BTN_MIDDLE)) + return; + + status = libinput_device_config_middle_emulation_set_enabled( + device->libinput_device, + LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED); + if (status == LIBINPUT_CONFIG_STATUS_UNSUPPORTED) + return; + + litest_drain_events(li); + + /* trigger emulation, then real middle */ + for (button = BTN_LEFT; button <= BTN_RIGHT; button++) { + litest_button_click(device, BTN_LEFT, true); + litest_button_click(device, BTN_RIGHT, true); + + litest_assert_button_event(li, + BTN_MIDDLE, + LIBINPUT_BUTTON_STATE_PRESSED); + + litest_button_click(device, BTN_MIDDLE, true); + litest_assert_button_event(li, + BTN_MIDDLE, + LIBINPUT_BUTTON_STATE_RELEASED); + litest_assert_button_event(li, + BTN_MIDDLE, + LIBINPUT_BUTTON_STATE_PRESSED); + + litest_assert_empty_queue(li); + + /* middle still down, release left/right */ + litest_button_click(device, button, false); + litest_assert_empty_queue(li); + litest_button_click(device, button, true); + litest_assert_button_event(li, + button, + LIBINPUT_BUTTON_STATE_PRESSED); + litest_assert_empty_queue(li); + + /* release both */ + litest_button_click(device, BTN_LEFT, false); + litest_button_click(device, BTN_RIGHT, false); + litest_assert_button_event(li, + button, + LIBINPUT_BUTTON_STATE_RELEASED); + litest_assert_empty_queue(li); + + litest_button_click(device, BTN_MIDDLE, false); + litest_assert_button_event(li, + BTN_MIDDLE, + LIBINPUT_BUTTON_STATE_RELEASED); + litest_assert_empty_queue(li); + } +} +END_TEST + +START_TEST(middlebutton_default_enabled) +{ + struct litest_device *dev = litest_current_device(); + struct libinput_device *device = dev->libinput_device; + enum libinput_config_status status; + int available; + enum libinput_config_middle_emulation_state deflt, state; + + available = libinput_device_config_middle_emulation_is_available(device); + ck_assert(available); + + if (libevdev_has_event_code(dev->evdev, EV_KEY, BTN_MIDDLE)) + deflt = LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED; + else + deflt = LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED; + + state = libinput_device_config_middle_emulation_get_enabled(device); + ck_assert_int_eq(state, deflt); + + state = libinput_device_config_middle_emulation_get_default_enabled( + device); + ck_assert_int_eq(state, deflt); + + status = libinput_device_config_middle_emulation_set_enabled(device, + LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED); + ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS); + + status = libinput_device_config_middle_emulation_set_enabled(device, + LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED); + ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS); + + status = libinput_device_config_middle_emulation_set_enabled(device, 3); + ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_INVALID); +} +END_TEST + +START_TEST(middlebutton_default_clickpad) +{ + struct litest_device *dev = litest_current_device(); + struct libinput_device *device = dev->libinput_device; + enum libinput_config_status status; + enum libinput_config_middle_emulation_state state; + int available; + + available = libinput_device_config_middle_emulation_is_available(device); + ck_assert(!available); + + state = libinput_device_config_middle_emulation_get_enabled(device); + ck_assert_int_eq(state, LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED); + state = libinput_device_config_middle_emulation_get_default_enabled( + device); + ck_assert_int_eq(state, LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED); + + status = libinput_device_config_middle_emulation_set_enabled(device, + LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED); + ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_UNSUPPORTED); + + status = libinput_device_config_middle_emulation_set_enabled(device, + LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED); + ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS); + + status = libinput_device_config_middle_emulation_set_enabled(device, 3); + ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_INVALID); +} +END_TEST + +START_TEST(middlebutton_default_touchpad) +{ + struct litest_device *dev = litest_current_device(); + struct libinput_device *device = dev->libinput_device; + enum libinput_config_middle_emulation_state state; + int available; + + available = libinput_device_config_middle_emulation_is_available(device); + ck_assert(!available); + + if (libevdev_has_event_code(dev->evdev, EV_KEY, BTN_MIDDLE)) + return; + + state = libinput_device_config_middle_emulation_get_enabled( + device); + ck_assert_int_eq(state, LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED); + state = libinput_device_config_middle_emulation_get_default_enabled( + device); + ck_assert_int_eq(state, LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED); +} +END_TEST + +START_TEST(middlebutton_default_disabled) +{ + struct litest_device *dev = litest_current_device(); + struct libinput_device *device = dev->libinput_device; + enum libinput_config_middle_emulation_state state; + enum libinput_config_status status; + int available; + + available = libinput_device_config_middle_emulation_is_available(device); + ck_assert(!available); + state = libinput_device_config_middle_emulation_get_enabled(device); + ck_assert_int_eq(state, LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED); + state = libinput_device_config_middle_emulation_get_default_enabled( + device); + ck_assert_int_eq(state, LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED); + status = libinput_device_config_middle_emulation_set_enabled(device, + LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED); + ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS); + status = libinput_device_config_middle_emulation_set_enabled(device, + LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED); + ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_UNSUPPORTED); +} +END_TEST + +void +litest_setup_tests(void) +{ + struct range axis_range = {ABS_X, ABS_Y + 1}; litest_add("pointer:motion", pointer_motion_relative, LITEST_RELATIVE, LITEST_ANY); litest_add("pointer:motion", pointer_motion_absolute, LITEST_ABSOLUTE, LITEST_ANY); litest_add("pointer:motion", pointer_motion_unaccel, LITEST_RELATIVE, LITEST_ANY); litest_add("pointer:button", pointer_button, LITEST_BUTTON, LITEST_CLICKPAD); - litest_add_no_device("pointer:button_auto_release", pointer_button_auto_release); + litest_add_no_device("pointer:button", pointer_button_auto_release); + litest_add_no_device("pointer:button", pointer_seat_button_count); litest_add("pointer:scroll", pointer_scroll_wheel, LITEST_WHEEL, LITEST_ANY); litest_add("pointer:scroll", pointer_scroll_button, LITEST_RELATIVE|LITEST_BUTTON, LITEST_ANY); + litest_add("pointer:scroll", pointer_scroll_nowheel_defaults, LITEST_RELATIVE|LITEST_BUTTON, LITEST_WHEEL); litest_add("pointer:scroll", pointer_scroll_natural_defaults, LITEST_WHEEL, LITEST_ANY); litest_add("pointer:scroll", pointer_scroll_natural_enable_config, LITEST_WHEEL, LITEST_ANY); litest_add("pointer:scroll", pointer_scroll_natural_wheel, LITEST_WHEEL, LITEST_ANY); - litest_add_no_device("pointer:seat button count", pointer_seat_button_count); litest_add("pointer:calibration", pointer_no_calibration, LITEST_ANY, LITEST_TOUCH|LITEST_SINGLE_TOUCH|LITEST_ABSOLUTE|LITEST_PROTOCOL_A); @@ -878,8 +1346,19 @@ int main (int argc, char **argv) { litest_add("pointer:accel", pointer_accel_defaults, LITEST_RELATIVE, LITEST_ANY); litest_add("pointer:accel", pointer_accel_invalid, LITEST_RELATIVE, LITEST_ANY); - litest_add("pointer:accel", pointer_accel_defaults_absolute, LITEST_ABSOLUTE, LITEST_ANY); + litest_add("pointer:accel", pointer_accel_defaults_absolute, LITEST_ABSOLUTE, LITEST_RELATIVE); + litest_add("pointer:accel", pointer_accel_defaults_absolute_relative, LITEST_ABSOLUTE|LITEST_RELATIVE, LITEST_ANY); litest_add("pointer:accel", pointer_accel_direction_change, LITEST_RELATIVE, LITEST_ANY); - return litest_run(argc, argv); + litest_add("pointer:middlebutton", middlebutton, LITEST_BUTTON, LITEST_ANY); + litest_add("pointer:middlebutton", middlebutton_timeout, LITEST_BUTTON, LITEST_ANY); + litest_add("pointer:middlebutton", middlebutton_doubleclick, LITEST_BUTTON, LITEST_ANY); + litest_add("pointer:middlebutton", middlebutton_middleclick, LITEST_BUTTON, LITEST_ANY); + litest_add("pointer:middlebutton", middlebutton_middleclick_during, LITEST_BUTTON, LITEST_ANY); + litest_add("pointer:middlebutton", middlebutton_default_enabled, LITEST_BUTTON, LITEST_TOUCHPAD|LITEST_POINTINGSTICK); + litest_add("pointer:middlebutton", middlebutton_default_clickpad, LITEST_CLICKPAD, LITEST_ANY); + litest_add("pointer:middlebutton", middlebutton_default_touchpad, LITEST_TOUCHPAD, LITEST_CLICKPAD); + litest_add("pointer:middlebutton", middlebutton_default_disabled, LITEST_ANY, LITEST_BUTTON); + + litest_add_ranged("pointer:state", pointer_absolute_initial_state, LITEST_ABSOLUTE, LITEST_ANY, &axis_range); } diff --git a/test/tablet.c b/test/tablet.c index ba61e0e0..a4bf6b2b 100644 --- a/test/tablet.c +++ b/test/tablet.c @@ -1662,8 +1662,8 @@ START_TEST(artpen_rotation) } END_TEST -int -main(int argc, char **argv) +void +litest_setup_tests(void) { litest_add("tablet:tool", tool_ref, LITEST_TABLET | LITEST_TOOL_SERIAL, LITEST_ANY); litest_add_no_device("tablet:tool", tool_capabilities); @@ -1692,6 +1692,4 @@ main(int argc, char **argv) litest_add("tablet:airbrush", airbrush_wheel, LITEST_TABLET, LITEST_ANY); litest_add("tablet:artpen", artpen_tool, LITEST_TABLET, LITEST_ANY); litest_add("tablet:artpen", artpen_rotation, LITEST_TABLET, LITEST_ANY); - - return litest_run(argc, argv); } diff --git a/test/touch.c b/test/touch.c index ac97c52b..2c07e090 100644 --- a/test/touch.c +++ b/test/touch.c @@ -182,6 +182,9 @@ START_TEST(touch_double_touch_down_up) dev = litest_current_device(); libinput = dev->libinput; + /* note: this test is a false negative, libevdev will filter + * tracking IDs re-used in the same slot. */ + litest_touch_down(dev, 0, 0, 0); litest_touch_down(dev, 0, 0, 0); litest_touch_up(dev, 0); @@ -241,9 +244,7 @@ START_TEST(touch_calibration_scale) litest_wait_for_event(li); ev = libinput_get_event(li); - ck_assert_int_eq(libinput_event_get_type(ev), - LIBINPUT_EVENT_TOUCH_DOWN); - tev = libinput_event_get_touch_event(ev); + tev = litest_is_touch_event(ev, LIBINPUT_EVENT_TOUCH_DOWN); x = libinput_event_touch_get_x_transformed(tev, width); y = libinput_event_touch_get_y_transformed(tev, height); @@ -312,9 +313,7 @@ START_TEST(touch_calibration_rotation) litest_touch_up(dev, 0); litest_wait_for_event(li); ev = libinput_get_event(li); - ck_assert_int_eq(libinput_event_get_type(ev), - LIBINPUT_EVENT_TOUCH_DOWN); - tev = libinput_event_get_touch_event(ev); + tev = litest_is_touch_event(ev, LIBINPUT_EVENT_TOUCH_DOWN); x = libinput_event_touch_get_x_transformed(tev, width); y = libinput_event_touch_get_y_transformed(tev, height); @@ -378,9 +377,7 @@ START_TEST(touch_calibration_translation) litest_wait_for_event(li); ev = libinput_get_event(li); - ck_assert_int_eq(libinput_event_get_type(ev), - LIBINPUT_EVENT_TOUCH_DOWN); - tev = libinput_event_get_touch_event(ev); + tev = litest_is_touch_event(ev, LIBINPUT_EVENT_TOUCH_DOWN); x = libinput_event_touch_get_x_transformed(tev, width); y = libinput_event_touch_get_y_transformed(tev, height); @@ -439,6 +436,8 @@ START_TEST(fake_mt_exists) * have different capabilities */ ck_assert(libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_POINTER)); + + libinput_event_destroy(event); } END_TEST @@ -460,7 +459,8 @@ START_TEST(fake_mt_no_touch_events) litest_touch_up(dev, 0); litest_touch_up(dev, 1); - litest_assert_empty_queue(li); + litest_assert_only_typed_events(li, + LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE); } END_TEST @@ -493,7 +493,7 @@ START_TEST(touch_protocol_a_touch) litest_wait_for_event_of_type(li, LIBINPUT_EVENT_TOUCH_DOWN, -1); ev = libinput_get_event(li); - tev = libinput_event_get_touch_event(ev); + tev = litest_is_touch_event(ev, LIBINPUT_EVENT_TOUCH_DOWN); oldx = libinput_event_touch_get_x(tev); oldy = libinput_event_touch_get_y(tev); @@ -582,9 +582,79 @@ START_TEST(touch_protocol_a_2fg_touch) } END_TEST -int -main(int argc, char **argv) +START_TEST(touch_initial_state) { + struct litest_device *dev; + struct libinput *libinput1, *libinput2; + struct libinput_event *ev1, *ev2; + struct libinput_event_touch *t1, *t2; + struct libinput_device *device1, *device2; + int axis = _i; /* looped test */ + + dev = litest_current_device(); + device1 = dev->libinput_device; + libinput_device_config_tap_set_enabled(device1, + LIBINPUT_CONFIG_TAP_DISABLED); + + libinput1 = dev->libinput; + litest_touch_down(dev, 0, 40, 60); + litest_touch_up(dev, 0); + + /* device is now on some x/y value */ + litest_drain_events(libinput1); + + libinput2 = litest_create_context(); + device2 = libinput_path_add_device(libinput2, + libevdev_uinput_get_devnode( + dev->uinput)); + libinput_device_config_tap_set_enabled(device2, + LIBINPUT_CONFIG_TAP_DISABLED); + litest_drain_events(libinput2); + + if (axis == ABS_X) + litest_touch_down(dev, 0, 40, 70); + else + litest_touch_down(dev, 0, 70, 60); + litest_touch_up(dev, 0); + + litest_wait_for_event(libinput1); + litest_wait_for_event(libinput2); + + while (libinput_next_event_type(libinput1)) { + ev1 = libinput_get_event(libinput1); + ev2 = libinput_get_event(libinput2); + + t1 = litest_is_touch_event(ev1, 0); + t2 = litest_is_touch_event(ev2, 0); + + ck_assert_int_eq(libinput_event_get_type(ev1), + libinput_event_get_type(ev2)); + + if (libinput_event_get_type(ev1) == LIBINPUT_EVENT_TOUCH_UP || + libinput_event_get_type(ev1) == LIBINPUT_EVENT_TOUCH_FRAME) + break; + + ck_assert_int_eq(libinput_event_touch_get_x(t1), + libinput_event_touch_get_x(t2)); + ck_assert_int_eq(libinput_event_touch_get_y(t1), + libinput_event_touch_get_y(t2)); + + libinput_event_destroy(ev1); + libinput_event_destroy(ev2); + } + + libinput_event_destroy(ev1); + libinput_event_destroy(ev2); + + libinput_unref(libinput2); +} +END_TEST + +void +litest_setup_tests(void) +{ + struct range axes = { ABS_X, ABS_Y + 1}; + litest_add("touch:frame", touch_frame_events, LITEST_TOUCH, LITEST_ANY); litest_add_no_device("touch:abs-transform", touch_abs_transform); litest_add_no_device("touch:many-slots", touch_many_slots); @@ -605,5 +675,5 @@ main(int argc, char **argv) litest_add("touch:protocol a", touch_protocol_a_touch, LITEST_PROTOCOL_A, LITEST_ANY); litest_add("touch:protocol a", touch_protocol_a_2fg_touch, LITEST_PROTOCOL_A, LITEST_ANY); - return litest_run(argc, argv); + litest_add_ranged("touch:state", touch_initial_state, LITEST_TOUCH, LITEST_PROTOCOL_A, &axes); } diff --git a/test/touchpad.c b/test/touchpad.c index 6fa23016..c8ecb322 100644 --- a/test/touchpad.c +++ b/test/touchpad.c @@ -123,6 +123,544 @@ START_TEST(touchpad_1fg_tap) } END_TEST +START_TEST(touchpad_1fg_doubletap) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + struct libinput_event_pointer *ptrev; + uint32_t oldtime, curtime; + + libinput_device_config_tap_set_enabled(dev->libinput_device, + LIBINPUT_CONFIG_TAP_ENABLED); + + litest_drain_events(li); + + litest_touch_down(dev, 0, 50, 50); + litest_touch_up(dev, 0); + litest_touch_down(dev, 0, 50, 50); + litest_touch_up(dev, 0); + libinput_dispatch(li); + + litest_timeout_tap(); + + libinput_dispatch(li); + event = libinput_get_event(li); + ptrev = litest_is_button_event(event, + BTN_LEFT, + LIBINPUT_BUTTON_STATE_PRESSED); + oldtime = libinput_event_pointer_get_time(ptrev); + libinput_event_destroy(event); + + event = libinput_get_event(li); + ptrev = litest_is_button_event(event, + BTN_LEFT, + LIBINPUT_BUTTON_STATE_RELEASED); + curtime = libinput_event_pointer_get_time(ptrev); + libinput_event_destroy(event); + ck_assert_int_le(oldtime, curtime); + + event = libinput_get_event(li); + ptrev = litest_is_button_event(event, + BTN_LEFT, + LIBINPUT_BUTTON_STATE_PRESSED); + curtime = libinput_event_pointer_get_time(ptrev); + libinput_event_destroy(event); + ck_assert_int_lt(oldtime, curtime); + oldtime = curtime; + + event = libinput_get_event(li); + ptrev = litest_is_button_event(event, + BTN_LEFT, + LIBINPUT_BUTTON_STATE_RELEASED); + curtime = libinput_event_pointer_get_time(ptrev); + libinput_event_destroy(event); + ck_assert_int_le(oldtime, curtime); + + litest_assert_empty_queue(li); +} +END_TEST + +START_TEST(touchpad_1fg_multitap) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + struct libinput_event_pointer *ptrev; + uint32_t oldtime = 0, + curtime; + int range = _i, /* looped test */ + ntaps; + + libinput_device_config_tap_set_enabled(dev->libinput_device, + LIBINPUT_CONFIG_TAP_ENABLED); + + litest_drain_events(li); + + for (ntaps = 0; ntaps <= range; ntaps++) { + litest_touch_down(dev, 0, 50, 50); + litest_touch_up(dev, 0); + libinput_dispatch(li); + msleep(10); + } + + litest_timeout_tap(); + libinput_dispatch(li); + + for (ntaps = 0; ntaps <= range; ntaps++) { + event = libinput_get_event(li); + ptrev = litest_is_button_event(event, + BTN_LEFT, + LIBINPUT_BUTTON_STATE_PRESSED); + curtime = libinput_event_pointer_get_time(ptrev); + libinput_event_destroy(event); + ck_assert_int_gt(curtime, oldtime); + + event = libinput_get_event(li); + ptrev = litest_is_button_event(event, + BTN_LEFT, + LIBINPUT_BUTTON_STATE_RELEASED); + curtime = libinput_event_pointer_get_time(ptrev); + libinput_event_destroy(event); + ck_assert_int_ge(curtime, oldtime); + oldtime = curtime; + } + litest_timeout_tap(); + litest_assert_empty_queue(li); +} +END_TEST + +START_TEST(touchpad_1fg_multitap_n_drag_move) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + struct libinput_event_pointer *ptrev; + uint32_t oldtime = 0, + curtime; + int range = _i, /* looped test */ + ntaps; + + libinput_device_config_tap_set_enabled(dev->libinput_device, + LIBINPUT_CONFIG_TAP_ENABLED); + + litest_drain_events(li); + + for (ntaps = 0; ntaps <= range; ntaps++) { + litest_touch_down(dev, 0, 50, 50); + litest_touch_up(dev, 0); + libinput_dispatch(li); + msleep(10); + } + + libinput_dispatch(li); + litest_touch_down(dev, 0, 50, 50); + litest_touch_move_to(dev, 0, 50, 50, 70, 50, 10, 4); + libinput_dispatch(li); + + for (ntaps = 0; ntaps <= range; ntaps++) { + event = libinput_get_event(li); + ptrev = litest_is_button_event(event, + BTN_LEFT, + LIBINPUT_BUTTON_STATE_PRESSED); + curtime = libinput_event_pointer_get_time(ptrev); + libinput_event_destroy(event); + ck_assert_int_gt(curtime, oldtime); + + event = libinput_get_event(li); + ptrev = litest_is_button_event(event, + BTN_LEFT, + LIBINPUT_BUTTON_STATE_RELEASED); + curtime = libinput_event_pointer_get_time(ptrev); + libinput_event_destroy(event); + ck_assert_int_ge(curtime, oldtime); + oldtime = curtime; + } + + event = libinput_get_event(li); + ptrev = litest_is_button_event(event, + BTN_LEFT, + LIBINPUT_BUTTON_STATE_PRESSED); + curtime = libinput_event_pointer_get_time(ptrev); + libinput_event_destroy(event); + ck_assert_int_gt(curtime, oldtime); + + litest_assert_only_typed_events(li, + LIBINPUT_EVENT_POINTER_MOTION); + + litest_touch_up(dev, 0); + litest_assert_button_event(li, + BTN_LEFT, + LIBINPUT_BUTTON_STATE_RELEASED); + + litest_assert_empty_queue(li); +} +END_TEST + +START_TEST(touchpad_1fg_multitap_n_drag_2fg) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + struct libinput_event_pointer *ptrev; + uint32_t oldtime = 0, + curtime; + int range = _i, + ntaps; + + libinput_device_config_tap_set_enabled(dev->libinput_device, + LIBINPUT_CONFIG_TAP_ENABLED); + + litest_drain_events(li); + + for (ntaps = 0; ntaps <= range; ntaps++) { + litest_touch_down(dev, 0, 50, 50); + litest_touch_up(dev, 0); + libinput_dispatch(li); + msleep(10); + } + + libinput_dispatch(li); + litest_touch_down(dev, 0, 50, 50); + msleep(10); + litest_touch_down(dev, 1, 70, 50); + libinput_dispatch(li); + + for (ntaps = 0; ntaps <= range; ntaps++) { + event = libinput_get_event(li); + ptrev = litest_is_button_event(event, + BTN_LEFT, + LIBINPUT_BUTTON_STATE_PRESSED); + curtime = libinput_event_pointer_get_time(ptrev); + libinput_event_destroy(event); + ck_assert_int_gt(curtime, oldtime); + + event = libinput_get_event(li); + ptrev = litest_is_button_event(event, + BTN_LEFT, + LIBINPUT_BUTTON_STATE_RELEASED); + curtime = libinput_event_pointer_get_time(ptrev); + libinput_event_destroy(event); + ck_assert_int_ge(curtime, oldtime); + oldtime = curtime; + } + + event = libinput_get_event(li); + ptrev = litest_is_button_event(event, + BTN_LEFT, + LIBINPUT_BUTTON_STATE_PRESSED); + curtime = libinput_event_pointer_get_time(ptrev); + libinput_event_destroy(event); + ck_assert_int_gt(curtime, oldtime); + + litest_touch_move_to(dev, 1, 70, 50, 90, 50, 10, 4); + + litest_assert_only_typed_events(li, + LIBINPUT_EVENT_POINTER_MOTION); + + litest_touch_up(dev, 1); + litest_touch_up(dev, 0); + litest_timeout_tap(); + litest_assert_button_event(li, + BTN_LEFT, + LIBINPUT_BUTTON_STATE_RELEASED); + + litest_assert_empty_queue(li); +} +END_TEST + +START_TEST(touchpad_1fg_multitap_n_drag_click) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + struct libinput_event_pointer *ptrev; + uint32_t oldtime = 0, + curtime; + int range = _i, /* looped test */ + ntaps; + + libinput_device_config_tap_set_enabled(dev->libinput_device, + LIBINPUT_CONFIG_TAP_ENABLED); + + litest_drain_events(li); + + for (ntaps = 0; ntaps <= range; ntaps++) { + litest_touch_down(dev, 0, 50, 50); + litest_touch_up(dev, 0); + libinput_dispatch(li); + msleep(10); + } + + litest_touch_down(dev, 0, 50, 50); + libinput_dispatch(li); + litest_button_click(dev, BTN_LEFT, true); + litest_button_click(dev, BTN_LEFT, false); + libinput_dispatch(li); + + for (ntaps = 0; ntaps <= range; ntaps++) { + event = libinput_get_event(li); + ptrev = litest_is_button_event(event, + BTN_LEFT, + LIBINPUT_BUTTON_STATE_PRESSED); + curtime = libinput_event_pointer_get_time(ptrev); + libinput_event_destroy(event); + ck_assert_int_gt(curtime, oldtime); + + event = libinput_get_event(li); + ptrev = litest_is_button_event(event, + BTN_LEFT, + LIBINPUT_BUTTON_STATE_RELEASED); + curtime = libinput_event_pointer_get_time(ptrev); + libinput_event_destroy(event); + ck_assert_int_ge(curtime, oldtime); + oldtime = curtime; + } + + litest_assert_button_event(li, + BTN_LEFT, + LIBINPUT_BUTTON_STATE_PRESSED); + litest_assert_button_event(li, + BTN_LEFT, + LIBINPUT_BUTTON_STATE_RELEASED); + litest_touch_up(dev, 0); + litest_timeout_tap(); + + litest_assert_empty_queue(li); +} +END_TEST + +START_TEST(touchpad_1fg_multitap_n_drag_timeout) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + struct libinput_event_pointer *ptrev; + uint32_t oldtime = 0, + curtime; + int range = _i, /* looped test */ + ntaps; + + libinput_device_config_tap_set_enabled(dev->libinput_device, + LIBINPUT_CONFIG_TAP_ENABLED); + + litest_drain_events(li); + + for (ntaps = 0; ntaps <= range; ntaps++) { + litest_touch_down(dev, 0, 50, 50); + litest_touch_up(dev, 0); + libinput_dispatch(li); + msleep(10); + } + + libinput_dispatch(li); + litest_touch_down(dev, 0, 50, 50); + libinput_dispatch(li); + + litest_timeout_tap(); + libinput_dispatch(li); + + for (ntaps = 0; ntaps <= range; ntaps++) { + event = libinput_get_event(li); + ptrev = litest_is_button_event(event, + BTN_LEFT, + LIBINPUT_BUTTON_STATE_PRESSED); + curtime = libinput_event_pointer_get_time(ptrev); + libinput_event_destroy(event); + ck_assert_int_gt(curtime, oldtime); + + event = libinput_get_event(li); + ptrev = litest_is_button_event(event, + BTN_LEFT, + LIBINPUT_BUTTON_STATE_RELEASED); + curtime = libinput_event_pointer_get_time(ptrev); + libinput_event_destroy(event); + ck_assert_int_ge(curtime, oldtime); + oldtime = curtime; + } + + event = libinput_get_event(li); + ptrev = litest_is_button_event(event, + BTN_LEFT, + LIBINPUT_BUTTON_STATE_PRESSED); + curtime = libinput_event_pointer_get_time(ptrev); + libinput_event_destroy(event); + ck_assert_int_gt(curtime, oldtime); + + litest_touch_move_to(dev, 0, 50, 50, 70, 50, 10, 4); + + litest_assert_only_typed_events(li, + LIBINPUT_EVENT_POINTER_MOTION); + + litest_touch_up(dev, 0); + litest_assert_button_event(li, + BTN_LEFT, + LIBINPUT_BUTTON_STATE_RELEASED); + + litest_assert_empty_queue(li); +} +END_TEST + +START_TEST(touchpad_1fg_multitap_n_drag_tap) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + struct libinput_event_pointer *ptrev; + uint32_t oldtime = 0, + curtime; + int range = _i, /* looped test */ + ntaps; + + libinput_device_config_tap_set_enabled(dev->libinput_device, + LIBINPUT_CONFIG_TAP_ENABLED); + + litest_drain_events(li); + + for (ntaps = 0; ntaps <= range; ntaps++) { + litest_touch_down(dev, 0, 50, 50); + litest_touch_up(dev, 0); + libinput_dispatch(li); + msleep(10); + } + + libinput_dispatch(li); + litest_touch_down(dev, 0, 50, 50); + libinput_dispatch(li); + + litest_timeout_tap(); + libinput_dispatch(li); + + for (ntaps = 0; ntaps <= range; ntaps++) { + event = libinput_get_event(li); + ptrev = litest_is_button_event(event, + BTN_LEFT, + LIBINPUT_BUTTON_STATE_PRESSED); + curtime = libinput_event_pointer_get_time(ptrev); + libinput_event_destroy(event); + ck_assert_int_gt(curtime, oldtime); + + event = libinput_get_event(li); + ptrev = litest_is_button_event(event, + BTN_LEFT, + LIBINPUT_BUTTON_STATE_RELEASED); + curtime = libinput_event_pointer_get_time(ptrev); + libinput_event_destroy(event); + ck_assert_int_ge(curtime, oldtime); + oldtime = curtime; + } + + event = libinput_get_event(li); + ptrev = litest_is_button_event(event, + BTN_LEFT, + LIBINPUT_BUTTON_STATE_PRESSED); + curtime = libinput_event_pointer_get_time(ptrev); + libinput_event_destroy(event); + ck_assert_int_gt(curtime, oldtime); + + litest_touch_move_to(dev, 0, 50, 50, 70, 50, 10, 4); + + litest_assert_only_typed_events(li, + LIBINPUT_EVENT_POINTER_MOTION); + + litest_touch_up(dev, 0); + litest_touch_down(dev, 0, 70, 50); + litest_touch_up(dev, 0); + litest_assert_button_event(li, + BTN_LEFT, + LIBINPUT_BUTTON_STATE_RELEASED); + + litest_assert_empty_queue(li); +} +END_TEST + +START_TEST(touchpad_1fg_multitap_n_drag_tap_click) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + struct libinput_event_pointer *ptrev; + uint32_t oldtime = 0, + curtime; + int i, ntaps; + + libinput_device_config_tap_set_enabled(dev->libinput_device, + LIBINPUT_CONFIG_TAP_ENABLED); + + litest_drain_events(li); + + for (i = 3; i < 5; i++) { + + for (ntaps = 0; ntaps <= i; ntaps++) { + litest_touch_down(dev, 0, 50, 50); + litest_touch_up(dev, 0); + libinput_dispatch(li); + msleep(10); + } + + libinput_dispatch(li); + litest_touch_down(dev, 0, 50, 50); + libinput_dispatch(li); + + litest_timeout_tap(); + libinput_dispatch(li); + + for (ntaps = 0; ntaps <= i; ntaps++) { + event = libinput_get_event(li); + ptrev = litest_is_button_event(event, + BTN_LEFT, + LIBINPUT_BUTTON_STATE_PRESSED); + curtime = libinput_event_pointer_get_time(ptrev); + libinput_event_destroy(event); + ck_assert_int_gt(curtime, oldtime); + + event = libinput_get_event(li); + ptrev = litest_is_button_event(event, + BTN_LEFT, + LIBINPUT_BUTTON_STATE_RELEASED); + curtime = libinput_event_pointer_get_time(ptrev); + libinput_event_destroy(event); + ck_assert_int_ge(curtime, oldtime); + oldtime = curtime; + } + + event = libinput_get_event(li); + ptrev = litest_is_button_event(event, + BTN_LEFT, + LIBINPUT_BUTTON_STATE_PRESSED); + curtime = libinput_event_pointer_get_time(ptrev); + libinput_event_destroy(event); + ck_assert_int_gt(curtime, oldtime); + + litest_touch_move_to(dev, 0, 50, 50, 70, 50, 10, 4); + + litest_assert_only_typed_events(li, + LIBINPUT_EVENT_POINTER_MOTION); + + litest_touch_up(dev, 0); + litest_touch_down(dev, 0, 70, 50); + litest_button_click(dev, BTN_LEFT, true); + litest_button_click(dev, BTN_LEFT, false); + libinput_dispatch(li); + + litest_assert_button_event(li, + BTN_LEFT, + LIBINPUT_BUTTON_STATE_RELEASED); + + /* the physical click */ + litest_assert_button_event(li, + BTN_LEFT, + LIBINPUT_BUTTON_STATE_PRESSED); + litest_assert_button_event(li, + BTN_LEFT, + LIBINPUT_BUTTON_STATE_RELEASED); + litest_touch_up(dev, 0); + + litest_assert_empty_queue(li); + } +} +END_TEST + START_TEST(touchpad_1fg_tap_n_drag) { struct litest_device *dev = litest_current_device(); @@ -164,6 +702,101 @@ START_TEST(touchpad_1fg_tap_n_drag) } END_TEST +START_TEST(touchpad_1fg_tap_n_drag_tap) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + + libinput_device_config_tap_set_enabled(dev->libinput_device, + LIBINPUT_CONFIG_TAP_ENABLED); + + litest_drain_events(li); + + litest_touch_down(dev, 0, 50, 50); + litest_touch_up(dev, 0); + litest_touch_down(dev, 0, 50, 50); + litest_touch_move_to(dev, 0, 50, 50, 80, 80, 5, 40); + litest_touch_up(dev, 0); + + libinput_dispatch(li); + + litest_assert_button_event(li, BTN_LEFT, + LIBINPUT_BUTTON_STATE_PRESSED); + + libinput_dispatch(li); + + litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION); + + /* lift finger, set down again, should continue dragging */ + litest_touch_down(dev, 0, 50, 50); + litest_touch_move_to(dev, 0, 50, 50, 80, 80, 5, 40); + + litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION); + + litest_touch_up(dev, 0); + litest_touch_down(dev, 0, 50, 50); + litest_touch_up(dev, 0); + + litest_assert_button_event(li, BTN_LEFT, + LIBINPUT_BUTTON_STATE_RELEASED); + + litest_assert_empty_queue(li); +} +END_TEST + +START_TEST(touchpad_1fg_tap_n_drag_tap_click) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + + libinput_device_config_tap_set_enabled(dev->libinput_device, + LIBINPUT_CONFIG_TAP_ENABLED); + + litest_drain_events(li); + + litest_touch_down(dev, 0, 50, 50); + litest_touch_up(dev, 0); + litest_touch_down(dev, 0, 50, 50); + litest_touch_move_to(dev, 0, 50, 50, 80, 80, 5, 40); + litest_touch_up(dev, 0); + + libinput_dispatch(li); + + litest_assert_button_event(li, BTN_LEFT, + LIBINPUT_BUTTON_STATE_PRESSED); + + libinput_dispatch(li); + + litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION); + + /* lift finger, set down again, should continue dragging */ + litest_touch_down(dev, 0, 50, 50); + litest_touch_move_to(dev, 0, 50, 50, 80, 80, 5, 40); + + litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION); + + litest_touch_up(dev, 0); + litest_touch_down(dev, 0, 50, 50); + litest_button_click(dev, BTN_LEFT, true); + litest_button_click(dev, BTN_LEFT, false); + libinput_dispatch(li); + + litest_assert_button_event(li, BTN_LEFT, + LIBINPUT_BUTTON_STATE_RELEASED); + + /* the physical click */ + litest_assert_button_event(li, + BTN_LEFT, + LIBINPUT_BUTTON_STATE_PRESSED); + litest_assert_button_event(li, + BTN_LEFT, + LIBINPUT_BUTTON_STATE_RELEASED); + litest_touch_up(dev, 0); + + litest_assert_empty_queue(li); +} +END_TEST + START_TEST(touchpad_1fg_tap_n_drag_timeout) { struct litest_device *dev = litest_current_device(); @@ -186,6 +819,7 @@ START_TEST(touchpad_1fg_tap_n_drag_timeout) litest_assert_empty_queue(li); litest_touch_up(dev, 0); + litest_timeout_tapndrag(); litest_assert_button_event(li, BTN_LEFT, LIBINPUT_BUTTON_STATE_RELEASED); @@ -369,6 +1003,38 @@ START_TEST(touchpad_2fg_tap_inverted) } END_TEST +START_TEST(touchpad_2fg_tap_quickrelease) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + + libinput_device_config_tap_set_enabled(dev->libinput_device, + LIBINPUT_CONFIG_TAP_ENABLED); + + litest_drain_events(dev->libinput); + + litest_touch_down(dev, 0, 50, 50); + litest_touch_down(dev, 1, 70, 70); + litest_event(dev, EV_ABS, ABS_MT_SLOT, 0); + litest_event(dev, EV_ABS, ABS_MT_TRACKING_ID, -1); + litest_event(dev, EV_ABS, ABS_MT_SLOT, 1); + litest_event(dev, EV_ABS, ABS_MT_TRACKING_ID, -1); + litest_event(dev, EV_KEY, BTN_TOOL_DOUBLETAP, 0); + litest_event(dev, EV_KEY, BTN_TOUCH, 0); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + + libinput_dispatch(li); + + litest_assert_button_event(li, BTN_RIGHT, + LIBINPUT_BUTTON_STATE_PRESSED); + litest_timeout_tap(); + litest_assert_button_event(li, BTN_RIGHT, + LIBINPUT_BUTTON_STATE_RELEASED); + + litest_assert_empty_queue(li); +} +END_TEST + START_TEST(touchpad_1fg_tap_click) { struct litest_device *dev = litest_current_device(); @@ -705,6 +1371,46 @@ START_TEST(touchpad_3fg_tap) } END_TEST +START_TEST(touchpad_3fg_tap_quickrelease) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + + if (libevdev_get_abs_maximum(dev->evdev, + ABS_MT_SLOT) <= 2) + return; + + libinput_device_config_tap_set_enabled(dev->libinput_device, + LIBINPUT_CONFIG_TAP_ENABLED); + + litest_drain_events(li); + + litest_touch_down(dev, 0, 50, 50); + litest_touch_down(dev, 1, 70, 50); + litest_touch_down(dev, 2, 80, 50); + litest_event(dev, EV_ABS, ABS_MT_SLOT, 0); + litest_event(dev, EV_ABS, ABS_MT_TRACKING_ID, -1); + litest_event(dev, EV_ABS, ABS_MT_SLOT, 1); + litest_event(dev, EV_ABS, ABS_MT_TRACKING_ID, -1); + litest_event(dev, EV_ABS, ABS_MT_SLOT, 2); + litest_event(dev, EV_ABS, ABS_MT_TRACKING_ID, -1); + litest_event(dev, EV_KEY, BTN_TOOL_TRIPLETAP, 0); + litest_event(dev, EV_KEY, BTN_TOUCH, 0); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + + libinput_dispatch(li); + + litest_assert_button_event(li, BTN_MIDDLE, + LIBINPUT_BUTTON_STATE_PRESSED); + litest_timeout_tap(); + litest_assert_button_event(li, BTN_MIDDLE, + LIBINPUT_BUTTON_STATE_RELEASED); + + libinput_dispatch(li); + litest_assert_empty_queue(li); +} +END_TEST + START_TEST(touchpad_3fg_tap_btntool) { struct litest_device *dev = litest_current_device(); @@ -783,6 +1489,161 @@ START_TEST(touchpad_3fg_tap_btntool_inverted) } END_TEST +START_TEST(touchpad_4fg_tap) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + int i; + + if (libevdev_get_abs_maximum(dev->evdev, + ABS_MT_SLOT) <= 3) + return; + + libinput_device_config_tap_set_enabled(dev->libinput_device, + LIBINPUT_CONFIG_TAP_ENABLED); + + for (i = 0; i < 4; i++) { + litest_drain_events(li); + + litest_touch_down(dev, 0, 50, 50); + litest_touch_down(dev, 1, 70, 50); + litest_touch_down(dev, 2, 80, 50); + litest_touch_down(dev, 3, 90, 50); + + litest_touch_up(dev, (i + 3) % 4); + litest_touch_up(dev, (i + 2) % 4); + litest_touch_up(dev, (i + 1) % 4); + litest_touch_up(dev, (i + 0) % 4); + + libinput_dispatch(li); + litest_assert_empty_queue(li); + litest_timeout_tap(); + litest_assert_empty_queue(li); + event = libinput_get_event(li); + ck_assert(event == NULL); + } +} +END_TEST + +START_TEST(touchpad_4fg_tap_quickrelease) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + + if (libevdev_get_abs_maximum(dev->evdev, + ABS_MT_SLOT) <= 3) + return; + + libinput_device_config_tap_set_enabled(dev->libinput_device, + LIBINPUT_CONFIG_TAP_ENABLED); + + litest_drain_events(li); + + litest_touch_down(dev, 0, 50, 50); + litest_touch_down(dev, 1, 70, 50); + litest_touch_down(dev, 2, 80, 50); + litest_touch_down(dev, 3, 90, 50); + + litest_event(dev, EV_ABS, ABS_MT_SLOT, 0); + litest_event(dev, EV_ABS, ABS_MT_TRACKING_ID, -1); + litest_event(dev, EV_ABS, ABS_MT_SLOT, 1); + litest_event(dev, EV_ABS, ABS_MT_TRACKING_ID, -1); + litest_event(dev, EV_ABS, ABS_MT_SLOT, 2); + litest_event(dev, EV_ABS, ABS_MT_TRACKING_ID, -1); + litest_event(dev, EV_ABS, ABS_MT_SLOT, 3); + litest_event(dev, EV_ABS, ABS_MT_TRACKING_ID, -1); + litest_event(dev, EV_KEY, BTN_TOOL_QUADTAP, 0); + litest_event(dev, EV_KEY, BTN_TOUCH, 0); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + + libinput_dispatch(li); + litest_assert_empty_queue(li); + litest_timeout_tap(); + litest_assert_empty_queue(li); +} +END_TEST + +START_TEST(touchpad_5fg_tap) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + int i; + + if (libevdev_get_abs_maximum(dev->evdev, + ABS_MT_SLOT) <= 4) + return; + + libinput_device_config_tap_set_enabled(dev->libinput_device, + LIBINPUT_CONFIG_TAP_ENABLED); + + for (i = 0; i < 5; i++) { + litest_drain_events(li); + + litest_touch_down(dev, 0, 20, 50); + litest_touch_down(dev, 1, 30, 50); + litest_touch_down(dev, 2, 40, 50); + litest_touch_down(dev, 3, 50, 50); + litest_touch_down(dev, 4, 60, 50); + + litest_touch_up(dev, (i + 4) % 5); + litest_touch_up(dev, (i + 3) % 5); + litest_touch_up(dev, (i + 2) % 5); + litest_touch_up(dev, (i + 1) % 5); + litest_touch_up(dev, (i + 0) % 5); + + libinput_dispatch(li); + litest_assert_empty_queue(li); + litest_timeout_tap(); + litest_assert_empty_queue(li); + event = libinput_get_event(li); + ck_assert(event == NULL); + } +} +END_TEST + +START_TEST(touchpad_5fg_tap_quickrelease) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + + if (libevdev_get_abs_maximum(dev->evdev, + ABS_MT_SLOT) <= 4) + return; + + libinput_device_config_tap_set_enabled(dev->libinput_device, + LIBINPUT_CONFIG_TAP_ENABLED); + + litest_drain_events(li); + + litest_touch_down(dev, 0, 20, 50); + litest_touch_down(dev, 1, 30, 50); + litest_touch_down(dev, 2, 40, 50); + litest_touch_down(dev, 3, 70, 50); + litest_touch_down(dev, 4, 90, 50); + + litest_event(dev, EV_ABS, ABS_MT_SLOT, 0); + litest_event(dev, EV_ABS, ABS_MT_TRACKING_ID, -1); + litest_event(dev, EV_ABS, ABS_MT_SLOT, 1); + litest_event(dev, EV_ABS, ABS_MT_TRACKING_ID, -1); + litest_event(dev, EV_ABS, ABS_MT_SLOT, 2); + litest_event(dev, EV_ABS, ABS_MT_TRACKING_ID, -1); + litest_event(dev, EV_ABS, ABS_MT_SLOT, 3); + litest_event(dev, EV_ABS, ABS_MT_TRACKING_ID, -1); + litest_event(dev, EV_ABS, ABS_MT_SLOT, 4); + litest_event(dev, EV_ABS, ABS_MT_TRACKING_ID, -1); + litest_event(dev, EV_KEY, BTN_TOOL_QUINTTAP, 0); + litest_event(dev, EV_KEY, BTN_TOUCH, 0); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + + libinput_dispatch(li); + litest_assert_empty_queue(li); + litest_timeout_tap(); + litest_assert_empty_queue(li); +} +END_TEST + START_TEST(touchpad_click_defaults_clickfinger) { struct litest_device *dev = litest_current_device(); @@ -891,6 +1752,32 @@ START_TEST(touchpad_1fg_clickfinger) } END_TEST +START_TEST(touchpad_1fg_clickfinger_no_touch) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + enum libinput_config_status status; + + status = libinput_device_config_click_set_method(dev->libinput_device, + LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER); + ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS); + + litest_drain_events(li); + + litest_event(dev, EV_KEY, BTN_LEFT, 1); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + litest_event(dev, EV_KEY, BTN_LEFT, 0); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + + libinput_dispatch(li); + + litest_assert_button_event(li, BTN_LEFT, + LIBINPUT_BUTTON_STATE_PRESSED); + litest_assert_button_event(li, BTN_LEFT, + LIBINPUT_BUTTON_STATE_RELEASED); +} +END_TEST + START_TEST(touchpad_2fg_clickfinger) { struct litest_device *dev = litest_current_device(); @@ -1170,6 +2057,9 @@ START_TEST(clickpad_btn_left) struct litest_device *dev = litest_current_device(); struct libinput *li = dev->libinput; + libinput_device_config_click_set_method(dev->libinput_device, + LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS); + litest_drain_events(li); /* A clickpad always needs a finger down to tell where the @@ -1820,7 +2710,7 @@ test_2fg_scroll(struct litest_device *dev, double dx, double dy, int want_sleep) litest_touch_down(dev, 0, 49, 50); litest_touch_down(dev, 1, 51, 50); - litest_touch_move_two_touches(dev, 49, 50, 51, 50, dx, dy, 5, 0); + litest_touch_move_two_touches(dev, 49, 50, 51, 50, dx, dy, 10, 0); /* Avoid a small scroll being seen as a tap */ if (want_sleep) { @@ -1935,6 +2825,26 @@ START_TEST(touchpad_2fg_scroll_source) } END_TEST +START_TEST(touchpad_2fg_scroll_semi_mt) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + + litest_drain_events(li); + + litest_touch_down(dev, 0, 20, 20); + litest_touch_down(dev, 1, 30, 20); + libinput_dispatch(li); + litest_touch_move_to(dev, 1, 30, 20, 30, 70, 10, 5); + + litest_assert_empty_queue(li); + + litest_touch_move_to(dev, 0, 20, 20, 20, 70, 10, 5); + + litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_AXIS); +} +END_TEST + START_TEST(touchpad_2fg_scroll_return_to_motion) { struct litest_device *dev = litest_current_device(); @@ -2023,12 +2933,26 @@ START_TEST(touchpad_scroll_natural) } END_TEST +static void +enable_edge_scroll(struct litest_device *dev) +{ + enum libinput_config_status status, expected; + struct libinput_device *device = dev->libinput_device; + + status = libinput_device_config_scroll_set_method(device, + LIBINPUT_CONFIG_SCROLL_EDGE); + + expected = LIBINPUT_CONFIG_STATUS_SUCCESS; + litest_assert_int_eq(status, expected); +} + START_TEST(touchpad_edge_scroll) { struct litest_device *dev = litest_current_device(); struct libinput *li = dev->libinput; litest_drain_events(li); + enable_edge_scroll(dev); litest_touch_down(dev, 0, 99, 20); litest_touch_move_to(dev, 0, 99, 20, 99, 80, 10, 0); @@ -2070,15 +2994,28 @@ START_TEST(touchpad_edge_scroll_timeout) struct libinput *li = dev->libinput; struct libinput_event *event; struct libinput_event_pointer *ptrev; + double width = 0, height = 0; + int y_movement = 30; /* in percent of height */ + + /* account for different touchpad heights, let's move 100% on a 15mm + high touchpad, less on anything else. This number is picked at + random, we just want deltas less than 5. + */ + if (libinput_device_get_size(dev->libinput_device, + &width, + &height) != -1) { + y_movement = 100 * 15/height; + } litest_drain_events(li); + enable_edge_scroll(dev); litest_touch_down(dev, 0, 99, 20); libinput_dispatch(li); litest_timeout_edgescroll(); libinput_dispatch(li); - litest_touch_move_to(dev, 0, 99, 20, 99, 80, 60, 10); + litest_touch_move_to(dev, 0, 99, 20, 99, 20 + y_movement, 60, 10); litest_touch_up(dev, 0); libinput_dispatch(li); @@ -2117,6 +3054,7 @@ START_TEST(touchpad_edge_scroll_no_motion) struct libinput *li = dev->libinput; litest_drain_events(li); + enable_edge_scroll(dev); litest_touch_down(dev, 0, 99, 10); litest_touch_move_to(dev, 0, 99, 10, 99, 70, 10, 0); @@ -2138,6 +3076,7 @@ START_TEST(touchpad_edge_scroll_no_edge_after_motion) struct libinput *li = dev->libinput; litest_drain_events(li); + enable_edge_scroll(dev); /* moving into the edge zone must not trigger scroll events */ litest_touch_down(dev, 0, 20, 20); @@ -2159,6 +3098,7 @@ START_TEST(touchpad_edge_scroll_source) struct libinput_event_pointer *ptrev; litest_drain_events(li); + enable_edge_scroll(dev); litest_touch_down(dev, 0, 99, 20); litest_touch_move_to(dev, 0, 99, 20, 99, 80, 10, 0); @@ -2177,6 +3117,26 @@ START_TEST(touchpad_edge_scroll_source) } END_TEST +START_TEST(touchpad_edge_scroll_no_2fg) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + + litest_drain_events(li); + enable_edge_scroll(dev); + + litest_touch_down(dev, 0, 49, 50); + litest_touch_down(dev, 1, 51, 50); + litest_touch_move_two_touches(dev, 49, 50, 51, 50, 20, 30, 5, 0); + libinput_dispatch(li); + litest_touch_up(dev, 0); + litest_touch_up(dev, 1); + libinput_dispatch(li); + + litest_assert_empty_queue(li); +} +END_TEST + START_TEST(touchpad_tap_is_available) { struct litest_device *dev = litest_current_device(); @@ -2195,6 +3155,9 @@ START_TEST(touchpad_tap_is_not_available) ck_assert_int_eq(libinput_device_config_tap_set_enabled(dev->libinput_device, LIBINPUT_CONFIG_TAP_ENABLED), LIBINPUT_CONFIG_STATUS_UNSUPPORTED); + ck_assert_int_eq(libinput_device_config_tap_set_enabled(dev->libinput_device, + LIBINPUT_CONFIG_TAP_DISABLED), + LIBINPUT_CONFIG_STATUS_SUCCESS); } END_TEST @@ -2242,7 +3205,7 @@ touchpad_has_palm_detect_size(struct litest_device *dev) rc = libinput_device_get_size(dev->libinput_device, &width, &height); - return rc == 0 && width >= 80; + return rc == 0 && width >= 70; } START_TEST(touchpad_palm_detect_at_edge) @@ -2399,6 +3362,49 @@ START_TEST(touchpad_palm_detect_no_palm_moving_into_edges) } END_TEST +START_TEST(touchpad_palm_detect_tap) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + + if (!touchpad_has_palm_detect_size(dev)) + return; + + libinput_device_config_tap_set_enabled(dev->libinput_device, + LIBINPUT_CONFIG_TAP_ENABLED); + + litest_drain_events(li); + + litest_touch_down(dev, 0, 95, 5); + litest_touch_up(dev, 0); + litest_assert_empty_queue(li); + + litest_touch_down(dev, 0, 5, 5); + litest_touch_up(dev, 0); + litest_assert_empty_queue(li); + + litest_touch_down(dev, 0, 5, 90); + litest_touch_up(dev, 0); + litest_assert_button_event(li, + BTN_LEFT, + LIBINPUT_BUTTON_STATE_PRESSED); + litest_assert_button_event(li, + BTN_LEFT, + LIBINPUT_BUTTON_STATE_RELEASED); + litest_assert_empty_queue(li); + + litest_touch_down(dev, 0, 95, 90); + litest_touch_up(dev, 0); + litest_assert_button_event(li, + BTN_LEFT, + LIBINPUT_BUTTON_STATE_PRESSED); + litest_assert_button_event(li, + BTN_LEFT, + LIBINPUT_BUTTON_STATE_RELEASED); + litest_assert_empty_queue(li); +} +END_TEST + START_TEST(touchpad_left_handed) { struct litest_device *dev = litest_current_device(); @@ -2625,6 +3631,9 @@ START_TEST(touchpad_left_handed_delayed) /* left-handed takes effect now */ litest_button_click(dev, BTN_RIGHT, 1); + libinput_dispatch(li); + litest_timeout_middlebutton(); + libinput_dispatch(li); litest_button_click(dev, BTN_LEFT, 1); libinput_dispatch(li); @@ -2721,7 +3730,7 @@ hover_start(struct litest_device *dev, unsigned int slot, /* WARNING: no SYN_REPORT! */ } -START_TEST(touchpad_hover_noevent) +START_TEST(touchpad_semi_mt_hover_noevent) { struct litest_device *dev = litest_current_device(); struct libinput *li = dev->libinput; @@ -2752,7 +3761,7 @@ START_TEST(touchpad_hover_noevent) } END_TEST -START_TEST(touchpad_hover_down) +START_TEST(touchpad_semi_mt_hover_down) { struct litest_device *dev = litest_current_device(); struct libinput *li = dev->libinput; @@ -2827,7 +3836,7 @@ START_TEST(touchpad_hover_down) } END_TEST -START_TEST(touchpad_hover_down_hover_down) +START_TEST(touchpad_semi_mt_hover_down_hover_down) { struct litest_device *dev = litest_current_device(); struct libinput *li = dev->libinput; @@ -2914,7 +3923,7 @@ START_TEST(touchpad_hover_down_hover_down) } END_TEST -START_TEST(touchpad_hover_down_up) +START_TEST(touchpad_semi_mt_hover_down_up) { struct litest_device *dev = litest_current_device(); struct libinput *li = dev->libinput; @@ -2981,7 +3990,7 @@ START_TEST(touchpad_hover_down_up) } END_TEST -START_TEST(touchpad_hover_2fg_noevent) +START_TEST(touchpad_semi_mt_hover_2fg_noevent) { struct litest_device *dev = litest_current_device(); struct libinput *li = dev->libinput; @@ -3026,7 +4035,7 @@ START_TEST(touchpad_hover_2fg_noevent) } END_TEST -START_TEST(touchpad_hover_2fg_1fg_down) +START_TEST(touchpad_semi_mt_hover_2fg_1fg_down) { struct litest_device *dev = litest_current_device(); struct libinput *li = dev->libinput; @@ -3075,6 +4084,179 @@ START_TEST(touchpad_hover_2fg_1fg_down) } END_TEST +START_TEST(touchpad_hover_noevent) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + + litest_drain_events(li); + + litest_hover_start(dev, 0, 50, 50); + litest_hover_move_to(dev, 0, 50, 50, 70, 70, 10, 10); + litest_hover_end(dev, 0); + + litest_assert_empty_queue(li); +} +END_TEST + +START_TEST(touchpad_hover_down) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + + litest_drain_events(li); + + /* hover the finger */ + litest_hover_start(dev, 0, 50, 50); + + litest_hover_move_to(dev, 0, 50, 50, 70, 70, 10, 10); + + litest_assert_empty_queue(li); + + /* touch the finger on the sensor */ + litest_touch_move_to(dev, 0, 70, 70, 50, 50, 10, 10); + + libinput_dispatch(li); + + litest_assert_only_typed_events(li, + LIBINPUT_EVENT_POINTER_MOTION); + + /* go back to hover */ + litest_hover_move_to(dev, 0, 50, 50, 70, 70, 10, 10); + litest_hover_end(dev, 0); + + litest_assert_empty_queue(li); +} +END_TEST + +START_TEST(touchpad_hover_down_hover_down) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + int i; + + litest_drain_events(li); + + litest_hover_start(dev, 0, 50, 50); + + for (i = 0; i < 3; i++) { + + /* hover the finger */ + litest_hover_move_to(dev, 0, 50, 50, 70, 70, 10, 10); + + litest_assert_empty_queue(li); + + /* touch the finger */ + litest_touch_move_to(dev, 0, 70, 70, 50, 50, 10, 10); + + libinput_dispatch(li); + + litest_assert_only_typed_events(li, + LIBINPUT_EVENT_POINTER_MOTION); + } + + litest_hover_end(dev, 0); + + /* start a new touch to be sure */ + litest_touch_down(dev, 0, 50, 50); + litest_touch_move_to(dev, 0, 50, 50, 70, 70, 10, 10); + litest_touch_up(dev, 0); + + litest_assert_only_typed_events(li, + LIBINPUT_EVENT_POINTER_MOTION); +} +END_TEST + +START_TEST(touchpad_hover_down_up) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + + litest_drain_events(li); + + /* hover two fingers, and a touch */ + litest_push_event_frame(dev); + litest_hover_start(dev, 0, 50, 50); + litest_hover_start(dev, 1, 50, 50); + litest_touch_down(dev, 2, 50, 50); + litest_pop_event_frame(dev);; + + litest_assert_empty_queue(li); + + /* hover first finger, end second and third in same frame */ + litest_push_event_frame(dev); + litest_hover_move(dev, 0, 70, 70); + litest_hover_end(dev, 1); + litest_touch_up(dev, 2); + litest_pop_event_frame(dev);; + + litest_assert_empty_queue(li); + + /* now move the finger */ + litest_touch_move_to(dev, 0, 50, 50, 70, 70, 10, 10); + + litest_touch_up(dev, 0); + + litest_assert_only_typed_events(li, + LIBINPUT_EVENT_POINTER_MOTION); +} +END_TEST + +START_TEST(touchpad_hover_2fg_noevent) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + + litest_drain_events(li); + + /* hover two fingers */ + litest_push_event_frame(dev); + litest_hover_start(dev, 0, 25, 25); + litest_hover_start(dev, 1, 50, 50); + litest_pop_event_frame(dev);; + + litest_hover_move_two_touches(dev, 25, 25, 50, 50, 50, 50, 10, 0); + + litest_push_event_frame(dev); + litest_hover_end(dev, 0); + litest_hover_end(dev, 1); + litest_pop_event_frame(dev);; + + litest_assert_empty_queue(li); +} +END_TEST + +START_TEST(touchpad_hover_2fg_1fg_down) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + int i; + + litest_drain_events(li); + + /* hover two fingers */ + litest_push_event_frame(dev); + litest_hover_start(dev, 0, 25, 25); + litest_touch_down(dev, 1, 50, 50); + litest_pop_event_frame(dev);; + + for (i = 0; i < 10; i++) { + litest_push_event_frame(dev); + litest_hover_move(dev, 0, 25 + 5 * i, 25 + 5 * i); + litest_touch_move(dev, 1, 50 + 5 * i, 50 - 5 * i); + litest_pop_event_frame(dev);; + } + + litest_push_event_frame(dev); + litest_hover_end(dev, 0); + litest_touch_up(dev, 1); + litest_pop_event_frame(dev);; + + litest_assert_only_typed_events(li, + LIBINPUT_EVENT_POINTER_MOTION); +} +END_TEST + static void assert_btnevent_from_device(struct litest_device *device, unsigned int button, @@ -3082,20 +4264,12 @@ assert_btnevent_from_device(struct litest_device *device, { struct libinput *li = device->libinput; struct libinput_event *e; - struct libinput_event_pointer *pev; libinput_dispatch(li); e = libinput_get_event(li); - ck_assert_notnull(e); - ck_assert_int_eq(libinput_event_get_type(e), - LIBINPUT_EVENT_POINTER_BUTTON); - pev = libinput_event_get_pointer_event(e); + litest_is_button_event(e, button, state); - ck_assert_ptr_eq(libinput_event_get_device(e), device->libinput_device); - ck_assert_int_eq(libinput_event_pointer_get_button(pev), - button); - ck_assert_int_eq(libinput_event_pointer_get_button_state(pev), - state); + litest_assert_ptr_eq(libinput_event_get_device(e), device->libinput_device); libinput_event_destroy(e); } @@ -3357,19 +4531,91 @@ START_TEST(touchpad_trackpoint_no_trackpoint) } END_TEST -int main(int argc, char **argv) { +START_TEST(touchpad_initial_state) +{ + struct litest_device *dev; + struct libinput *libinput1, *libinput2; + struct libinput_event *ev1, *ev2; + struct libinput_event_pointer *p1, *p2; + int axis = _i; /* looped test */ + int x = 40, y = 60; + + dev = litest_current_device(); + libinput1 = dev->libinput; + + libinput_device_config_tap_set_enabled(dev->libinput_device, + LIBINPUT_CONFIG_TAP_DISABLED); + + litest_touch_down(dev, 0, x, y); + litest_touch_up(dev, 0); + + /* device is now on some x/y value */ + litest_drain_events(libinput1); + + libinput2 = litest_create_context(); + libinput_path_add_device(libinput2, + libevdev_uinput_get_devnode(dev->uinput)); + litest_drain_events(libinput2); + + if (axis == ABS_X) + x = 30; + else + y = 30; + litest_touch_down(dev, 0, x, y); + litest_touch_move_to(dev, 0, x, y, 80, 80, 10, 1); + litest_touch_up(dev, 0); + + litest_wait_for_event(libinput1); + litest_wait_for_event(libinput2); + + while (libinput_next_event_type(libinput1)) { + ev1 = libinput_get_event(libinput1); + ev2 = libinput_get_event(libinput2); + + p1 = litest_is_motion_event(ev1); + p2 = litest_is_motion_event(ev2); + + ck_assert_int_eq(libinput_event_get_type(ev1), + libinput_event_get_type(ev2)); + + ck_assert_int_eq(libinput_event_pointer_get_dx(p1), + libinput_event_pointer_get_dx(p2)); + ck_assert_int_eq(libinput_event_pointer_get_dy(p1), + libinput_event_pointer_get_dy(p2)); + libinput_event_destroy(ev1); + libinput_event_destroy(ev2); + } + + libinput_unref(libinput2); +} +END_TEST + +void +litest_setup_tests(void) +{ + struct range multitap_range = {3, 8}; + struct range axis_range = {ABS_X, ABS_Y + 1}; litest_add("touchpad:motion", touchpad_1fg_motion, LITEST_TOUCHPAD, LITEST_ANY); litest_add("touchpad:motion", touchpad_2fg_no_motion, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH); litest_add("touchpad:tap", touchpad_1fg_tap, LITEST_TOUCHPAD, LITEST_ANY); + litest_add("touchpad:tap", touchpad_1fg_doubletap, LITEST_TOUCHPAD, LITEST_ANY); + litest_add_ranged("touchpad:tap", touchpad_1fg_multitap, LITEST_TOUCHPAD, LITEST_ANY, &multitap_range); + litest_add_ranged("touchpad:tap", touchpad_1fg_multitap_n_drag_timeout, LITEST_TOUCHPAD, LITEST_ANY, &multitap_range); + litest_add_ranged("touchpad:tap", touchpad_1fg_multitap_n_drag_tap, LITEST_TOUCHPAD, LITEST_ANY, &multitap_range); + litest_add_ranged("touchpad:tap", touchpad_1fg_multitap_n_drag_move, LITEST_TOUCHPAD, LITEST_ANY, &multitap_range); + litest_add_ranged("touchpad:tap", touchpad_1fg_multitap_n_drag_2fg, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH, &multitap_range); + litest_add_ranged("touchpad:tap", touchpad_1fg_multitap_n_drag_click, LITEST_CLICKPAD, LITEST_ANY, &multitap_range); litest_add("touchpad:tap", touchpad_1fg_tap_n_drag, LITEST_TOUCHPAD, LITEST_ANY); + litest_add("touchpad:tap", touchpad_1fg_tap_n_drag_tap, LITEST_TOUCHPAD, LITEST_ANY); litest_add("touchpad:tap", touchpad_1fg_tap_n_drag_timeout, LITEST_TOUCHPAD, LITEST_ANY); litest_add("touchpad:tap", touchpad_2fg_tap_n_drag, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH); litest_add("touchpad:tap", touchpad_2fg_tap_n_drag_3fg_btntool, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH|LITEST_APPLE_CLICKPAD); litest_add("touchpad:tap", touchpad_2fg_tap_n_drag_3fg, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH); litest_add("touchpad:tap", touchpad_2fg_tap, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH|LITEST_SEMI_MT); litest_add("touchpad:tap", touchpad_2fg_tap_inverted, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH); + litest_add("touchpad:tap", touchpad_2fg_tap_quickrelease, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH|LITEST_SEMI_MT); litest_add("touchpad:tap", touchpad_1fg_tap_click, LITEST_TOUCHPAD|LITEST_BUTTON, LITEST_CLICKPAD); litest_add("touchpad:tap", touchpad_2fg_tap_click, LITEST_TOUCHPAD|LITEST_BUTTON, LITEST_SINGLE_TOUCH|LITEST_CLICKPAD); @@ -3381,11 +4627,18 @@ int main(int argc, char **argv) { litest_add("touchpad:tap", touchpad_3fg_tap_btntool, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH); litest_add("touchpad:tap", touchpad_3fg_tap_btntool_inverted, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH); litest_add("touchpad:tap", touchpad_3fg_tap, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH); + litest_add("touchpad:tap", touchpad_3fg_tap_quickrelease, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH); + litest_add("touchpad:tap", touchpad_4fg_tap, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH|LITEST_SEMI_MT); + litest_add("touchpad:tap", touchpad_4fg_tap_quickrelease, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH|LITEST_SEMI_MT); + litest_add("touchpad:tap", touchpad_5fg_tap, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH|LITEST_SEMI_MT); + litest_add("touchpad:tap", touchpad_5fg_tap_quickrelease, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH|LITEST_SEMI_MT); /* Real buttons don't interfere with tapping, so don't run those for pads with buttons */ litest_add("touchpad:tap", touchpad_1fg_double_tap_click, LITEST_CLICKPAD, LITEST_ANY); litest_add("touchpad:tap", touchpad_1fg_tap_n_drag_click, LITEST_CLICKPAD, LITEST_ANY); + litest_add("touchpad:tap", touchpad_1fg_multitap_n_drag_tap_click, LITEST_CLICKPAD, LITEST_ANY); + litest_add("touchpad:tap", touchpad_1fg_tap_n_drag_tap_click, LITEST_CLICKPAD, LITEST_ANY); litest_add("touchpad:tap", touchpad_tap_default_disabled, LITEST_TOUCHPAD|LITEST_BUTTON, LITEST_ANY); litest_add("touchpad:tap", touchpad_tap_default_enabled, LITEST_TOUCHPAD, LITEST_BUTTON); @@ -3397,6 +4650,7 @@ int main(int argc, char **argv) { litest_add("touchpad:tap", clickpad_2fg_tap_click, LITEST_CLICKPAD, LITEST_SINGLE_TOUCH|LITEST_APPLE_CLICKPAD); litest_add("touchpad:clickfinger", touchpad_1fg_clickfinger, LITEST_CLICKPAD, LITEST_ANY); + litest_add("touchpad:clickfinger", touchpad_1fg_clickfinger_no_touch, LITEST_CLICKPAD, LITEST_ANY); litest_add("touchpad:clickfinger", touchpad_2fg_clickfinger, LITEST_CLICKPAD, LITEST_ANY); litest_add("touchpad:clickfinger", touchpad_clickfinger_to_area_method, LITEST_CLICKPAD, LITEST_ANY); litest_add("touchpad:clickfinger", @@ -3433,14 +4687,16 @@ int main(int argc, char **argv) { litest_add("touchpad:scroll", touchpad_2fg_scroll_slow_distance, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH); litest_add("touchpad:scroll", touchpad_2fg_scroll_return_to_motion, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH); litest_add("touchpad:scroll", touchpad_2fg_scroll_source, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH); + litest_add("touchpad:scroll", touchpad_2fg_scroll_semi_mt, LITEST_SEMI_MT, LITEST_SINGLE_TOUCH); litest_add("touchpad:scroll", touchpad_scroll_natural_defaults, LITEST_TOUCHPAD, LITEST_ANY); litest_add("touchpad:scroll", touchpad_scroll_natural_enable_config, LITEST_TOUCHPAD, LITEST_ANY); litest_add("touchpad:scroll", touchpad_scroll_natural, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH); - litest_add("touchpad:scroll", touchpad_edge_scroll, LITEST_TOUCHPAD|LITEST_SINGLE_TOUCH, LITEST_ANY); - litest_add("touchpad:scroll", touchpad_edge_scroll_no_motion, LITEST_TOUCHPAD|LITEST_SINGLE_TOUCH, LITEST_ANY); - litest_add("touchpad:scroll", touchpad_edge_scroll_no_edge_after_motion, LITEST_TOUCHPAD|LITEST_SINGLE_TOUCH, LITEST_ANY); - litest_add("touchpad:scroll", touchpad_edge_scroll_timeout, LITEST_TOUCHPAD|LITEST_SINGLE_TOUCH, LITEST_ANY); - litest_add("touchpad:scroll", touchpad_edge_scroll_source, LITEST_TOUCHPAD|LITEST_SINGLE_TOUCH, LITEST_ANY); + litest_add("touchpad:scroll", touchpad_edge_scroll, LITEST_TOUCHPAD, LITEST_CLICKPAD); + litest_add("touchpad:scroll", touchpad_edge_scroll_no_motion, LITEST_TOUCHPAD, LITEST_CLICKPAD); + litest_add("touchpad:scroll", touchpad_edge_scroll_no_edge_after_motion, LITEST_TOUCHPAD, LITEST_CLICKPAD); + litest_add("touchpad:scroll", touchpad_edge_scroll_timeout, LITEST_TOUCHPAD, LITEST_CLICKPAD); + litest_add("touchpad:scroll", touchpad_edge_scroll_source, LITEST_TOUCHPAD, LITEST_CLICKPAD); + litest_add("touchpad:scroll", touchpad_edge_scroll_no_2fg, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH|LITEST_CLICKPAD); litest_add("touchpad:palm", touchpad_palm_detect_at_edge, LITEST_TOUCHPAD, LITEST_ANY); litest_add("touchpad:palm", touchpad_palm_detect_at_bottom_corners, LITEST_TOUCHPAD, LITEST_CLICKPAD); @@ -3448,6 +4704,7 @@ int main(int argc, char **argv) { litest_add("touchpad:palm", touchpad_palm_detect_palm_becomes_pointer, LITEST_TOUCHPAD, LITEST_ANY); litest_add("touchpad:palm", touchpad_palm_detect_palm_stays_palm, LITEST_TOUCHPAD, LITEST_ANY); litest_add("touchpad:palm", touchpad_palm_detect_no_palm_moving_into_edges, LITEST_TOUCHPAD, LITEST_ANY); + litest_add("touchpad:palm", touchpad_palm_detect_tap, LITEST_TOUCHPAD, LITEST_ANY); litest_add("touchpad:left-handed", touchpad_left_handed, LITEST_TOUCHPAD|LITEST_BUTTON, LITEST_CLICKPAD); litest_add("touchpad:left-handed", touchpad_left_handed_clickpad, LITEST_CLICKPAD, LITEST_APPLE_CLICKPAD); @@ -3457,14 +4714,21 @@ int main(int argc, char **argv) { litest_add("touchpad:left-handed", touchpad_left_handed_delayed, LITEST_TOUCHPAD|LITEST_BUTTON, LITEST_CLICKPAD); litest_add("touchpad:left-handed", touchpad_left_handed_clickpad_delayed, LITEST_CLICKPAD, LITEST_APPLE_CLICKPAD); - /* Hover tests aren't generic, they only work on this device and + /* Semi-MT hover tests aren't generic, they only work on this device and * ignore the semi-mt capability (it doesn't matter for the tests */ - litest_add_for_device("touchpad:hover", touchpad_hover_noevent, LITEST_SYNAPTICS_HOVER_SEMI_MT); - litest_add_for_device("touchpad:hover", touchpad_hover_down, LITEST_SYNAPTICS_HOVER_SEMI_MT); - litest_add_for_device("touchpad:hover", touchpad_hover_down_up, LITEST_SYNAPTICS_HOVER_SEMI_MT); - litest_add_for_device("touchpad:hover", touchpad_hover_down_hover_down, LITEST_SYNAPTICS_HOVER_SEMI_MT); - litest_add_for_device("touchpad:hover", touchpad_hover_2fg_noevent, LITEST_SYNAPTICS_HOVER_SEMI_MT); - litest_add_for_device("touchpad:hover", touchpad_hover_2fg_1fg_down, LITEST_SYNAPTICS_HOVER_SEMI_MT); + litest_add_for_device("touchpad:semi-mt-hover", touchpad_semi_mt_hover_noevent, LITEST_SYNAPTICS_HOVER_SEMI_MT); + litest_add_for_device("touchpad:semi-mt-hover", touchpad_semi_mt_hover_down, LITEST_SYNAPTICS_HOVER_SEMI_MT); + litest_add_for_device("touchpad:semi-mt-hover", touchpad_semi_mt_hover_down_up, LITEST_SYNAPTICS_HOVER_SEMI_MT); + litest_add_for_device("touchpad:semi-mt-hover", touchpad_semi_mt_hover_down_hover_down, LITEST_SYNAPTICS_HOVER_SEMI_MT); + litest_add_for_device("touchpad:semi-mt-hover", touchpad_semi_mt_hover_2fg_noevent, LITEST_SYNAPTICS_HOVER_SEMI_MT); + litest_add_for_device("touchpad:semi-mt-hover", touchpad_semi_mt_hover_2fg_1fg_down, LITEST_SYNAPTICS_HOVER_SEMI_MT); + + litest_add("touchpad:hover", touchpad_hover_noevent, LITEST_TOUCHPAD|LITEST_HOVER, LITEST_ANY); + litest_add("touchpad:hover", touchpad_hover_down, LITEST_TOUCHPAD|LITEST_HOVER, LITEST_ANY); + litest_add("touchpad:hover", touchpad_hover_down_up, LITEST_TOUCHPAD|LITEST_HOVER, LITEST_ANY); + litest_add("touchpad:hover", touchpad_hover_down_hover_down, LITEST_TOUCHPAD|LITEST_HOVER, LITEST_ANY); + litest_add("touchpad:hover", touchpad_hover_2fg_noevent, LITEST_TOUCHPAD|LITEST_HOVER, LITEST_ANY); + litest_add("touchpad:hover", touchpad_hover_2fg_1fg_down, LITEST_TOUCHPAD|LITEST_HOVER, LITEST_ANY); litest_add_for_device("touchpad:trackpoint", touchpad_trackpoint_buttons, LITEST_SYNAPTICS_TRACKPOINT_BUTTONS); litest_add_for_device("touchpad:trackpoint", touchpad_trackpoint_mb_scroll, LITEST_SYNAPTICS_TRACKPOINT_BUTTONS); @@ -3473,5 +4737,5 @@ int main(int argc, char **argv) { litest_add_for_device("touchpad:trackpoint", touchpad_trackpoint_buttons_2fg_scroll, LITEST_SYNAPTICS_TRACKPOINT_BUTTONS); litest_add_for_device("touchpad:trackpoint", touchpad_trackpoint_no_trackpoint, LITEST_SYNAPTICS_TRACKPOINT_BUTTONS); - return litest_run(argc, argv); + litest_add_ranged("touchpad:state", touchpad_initial_state, LITEST_TOUCHPAD, LITEST_ANY, &axis_range); } diff --git a/test/trackpoint.c b/test/trackpoint.c index 2708bad2..9fcce6f7 100644 --- a/test/trackpoint.c +++ b/test/trackpoint.c @@ -130,12 +130,11 @@ START_TEST(trackpoint_scroll_source) } END_TEST -int main(int argc, char **argv) { - +void +litest_setup_tests(void) +{ litest_add("trackpoint:middlebutton", trackpoint_middlebutton, LITEST_POINTINGSTICK, LITEST_ANY); litest_add("trackpoint:middlebutton", trackpoint_middlebutton_noscroll, LITEST_POINTINGSTICK, LITEST_ANY); litest_add("trackpoint:scroll", trackpoint_scroll, LITEST_POINTINGSTICK, LITEST_ANY); litest_add("trackpoint:scroll", trackpoint_scroll_source, LITEST_POINTINGSTICK, LITEST_ANY); - - return litest_run(argc, argv); } diff --git a/test/udev.c b/test/udev.c index c351bed5..4ec956bc 100644 --- a/test/udev.c +++ b/test/udev.c @@ -502,8 +502,8 @@ START_TEST(udev_seat_recycle) } END_TEST -int -main(int argc, char **argv) +void +litest_setup_tests(void) { litest_add_no_device("udev:create", udev_create_NULL); litest_add_no_device("udev:create", udev_create_seat0); @@ -518,6 +518,4 @@ main(int argc, char **argv) litest_add_for_device("udev:suspend", udev_suspend_resume, LITEST_SYNAPTICS_CLICKPAD); litest_add_for_device("udev:device events", udev_device_sysname, LITEST_SYNAPTICS_CLICKPAD); litest_add_for_device("udev:seat", udev_seat_recycle, LITEST_SYNAPTICS_CLICKPAD); - - return litest_run(argc, argv); } diff --git a/test/valgrind.suppressions b/test/valgrind.suppressions index 4ad22ab2..b7f43499 100644 --- a/test/valgrind.suppressions +++ b/test/valgrind.suppressions @@ -31,3 +31,11 @@ ... fun:g_malloc0 } +{ + libunwind:msync_uninitialized_bytes + Memcheck:Param + msync(start) + fun:__msync_nocancel + ... + fun:litest_backtrace +} diff --git a/tools/.gitignore b/tools/.gitignore index cf348a6f..cb934298 100644 --- a/tools/.gitignore +++ b/tools/.gitignore @@ -1,2 +1,5 @@ event-debug event-gui +ptraccel-debug +libinput-list-devices +libinput-debug-events diff --git a/tools/Makefile.am b/tools/Makefile.am index cebcd729..b24c560d 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -1,19 +1,36 @@ -noinst_PROGRAMS = event-debug +noinst_PROGRAMS = event-debug ptraccel-debug +bin_PROGRAMS = libinput-list-devices libinput-debug-events noinst_LTLIBRARIES = libshared.la AM_CPPFLAGS = -I$(top_srcdir)/include \ - -I$(top_srcdir)/src - + -I$(top_srcdir)/src \ + -I$(top_builddir)/src # for libinput-version.h libshared_la_SOURCES = \ shared.c \ shared.h +libshared_la_CFLAGS = $(LIBEVDEV_CFLAGS) +libshared_la_LIBADD = $(LIBEVDEV_LIBS) event_debug_SOURCES = event-debug.c event_debug_LDADD = ../src/libinput.la libshared.la $(LIBUDEV_LIBS) event_debug_LDFLAGS = -no-install event_debug_CFLAGS = $(LIBUDEV_CFLAGS) +ptraccel_debug_SOURCES = ptraccel-debug.c +ptraccel_debug_LDADD = ../src/libfilter.la +ptraccel_debug_LDFLAGS = -no-install + +libinput_list_devices_SOURCES = libinput-list-devices.c +libinput_list_devices_LDADD = ../src/libinput.la libshared.la $(LIBUDEV_LIBS) +libinput_list_devices_CFLAGS = $(LIBUDEV_CFLAGS) +dist_man1_MANS = libinput-list-devices.man + +libinput_debug_events_SOURCES = $(event_debug_SOURCES) +libinput_debug_events_LDADD = $(event_debug_LDADD) +libinput_debug_events_CFLAGS = $(event_debug_CFLAGS) +dist_man1_MANS += libinput-debug-events.man + if BUILD_EVENTGUI noinst_PROGRAMS += event-gui diff --git a/tools/event-gui.c b/tools/event-gui.c index 370df4a7..cdeabf05 100644 --- a/tools/event-gui.c +++ b/tools/event-gui.c @@ -62,8 +62,8 @@ struct window { int absx, absy; /* scroll bar positions */ - int vx, vy; - int hx, hy; + double vx, vy; + double hx, hy; /* touch positions */ struct touch touches[32]; @@ -279,14 +279,8 @@ handle_event_device_notify(struct libinput_event *ev) libinput_device_get_name(dev), type); - if (libinput_device_config_tap_get_finger_count(dev) > 0) { - enum libinput_config_status status; - status = libinput_device_config_tap_set_enabled(dev, - LIBINPUT_CONFIG_TAP_ENABLED); - if (status != LIBINPUT_CONFIG_STATUS_SUCCESS) - error("%s: Failed to enable tapping\n", - libinput_device_get_sysname(dev)); - } + tools_device_apply_config(libinput_event_get_device(ev), + &options); li = libinput_event_get_context(ev); w = libinput_get_user_data(li); @@ -369,7 +363,7 @@ handle_event_axis(struct libinput_event *ev, struct window *w) LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL)) { value = libinput_event_pointer_get_axis_value(p, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL); - w->vy += (int)value; + w->vy += value; w->vy = clip(w->vy, 0, w->height); } @@ -377,7 +371,7 @@ handle_event_axis(struct libinput_event *ev, struct window *w) LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL)) { value = libinput_event_pointer_get_axis_value(p, LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL); - w->hx += (int)value; + w->hx += value; w->hx = clip(w->hx, 0, w->width); } } diff --git a/tools/libinput-debug-events.man b/tools/libinput-debug-events.man new file mode 100644 index 00000000..bb393992 --- /dev/null +++ b/tools/libinput-debug-events.man @@ -0,0 +1,31 @@ +.TH LIBINPUT-DEBUG-EVENTS "1" +.SH NAME +libinput-debug-events \- debug helper for libinput +.SH SYNOPSIS +.B libinput-debug-events [--help] +.SH DESCRIPTION +.PP +The +.I libinput-debug-events +tool creates a libinput context and prints all events from these devices. +.PP +This is a debugging tool only, its output may change at any time. Do not +rely on the output. +.PP +This tool usually needs to be run as root to have access to the +/dev/input/eventX nodes. +.SH OPTIONS +.TP 8 +.B --help +Print help +.PP +For all other options, see the output from --help. Options may be added or +removed at any time. +.SH NOTES +.PP +Events shown by this tool may not correspond to the events seen by a +different user of libinput. This tool initializes a separate context. +.PP +Events shown by this tool include key codes in plain text. Anything you type +while this tool is running will show up in the output, including your +passwords. diff --git a/tools/libinput-list-devices.c b/tools/libinput-list-devices.c new file mode 100644 index 00000000..825969d5 --- /dev/null +++ b/tools/libinput-list-devices.c @@ -0,0 +1,313 @@ +/* + * Copyright © 2015 Red Hat, Inc. + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "shared.h" + +static int +open_restricted(const char *path, int flags, void *user_data) +{ + int fd = open(path, flags); + if (fd < 0) + fprintf(stderr, "Failed to open %s (%s)\n", + path, strerror(errno)); + return fd < 0 ? -errno : fd; +} + +static void +close_restricted(int fd, void *user_data) +{ + close(fd); +} + +static const struct libinput_interface interface = { + .open_restricted = open_restricted, + .close_restricted = close_restricted, +}; + +static inline const char* +bool_to_str(bool b) +{ + if (b) + return "yes"; + else + return "no"; +} + +static const char * +tap_default(struct libinput_device *device) +{ + if (!libinput_device_config_tap_get_finger_count(device)) + return "n/a"; + + if (libinput_device_config_tap_get_default_enabled(device)) + return "enabled"; + else + return "disabled"; +} + +static const char* +left_handed_default(struct libinput_device *device) +{ + if (!libinput_device_config_left_handed_is_available(device)) + return "n/a"; + + if (libinput_device_config_left_handed_get_default(device)) + return "enabled"; + else + return "disabled"; +} + +static const char * +nat_scroll_default(struct libinput_device *device) +{ + if (!libinput_device_config_scroll_has_natural_scroll(device)) + return "n/a"; + + if (libinput_device_config_scroll_get_default_natural_scroll_enabled(device)) + return "enabled"; + else + return "disabled"; +} + +static const char * +middle_emulation_default(struct libinput_device *device) +{ + if (!libinput_device_config_middle_emulation_is_available(device)) + return "n/a"; + + if (libinput_device_config_middle_emulation_get_default_enabled(device)) + return "enabled"; + else + return "disabled"; +} + +static char * +calibration_default(struct libinput_device *device) +{ + char *str; + float calibration[6]; + + if (!libinput_device_config_calibration_has_matrix(device)) { + asprintf(&str, "n/a"); + return str; + } + + if (libinput_device_config_calibration_get_default_matrix(device, + calibration) == 0) { + asprintf(&str, "identity matrix"); + return str; + } + + asprintf(&str, + "%.2f %.2f %.2f %.2f %.2f %.2f", + calibration[0], + calibration[1], + calibration[2], + calibration[3], + calibration[4], + calibration[5]); + return str; +} + +static char * +scroll_defaults(struct libinput_device *device) +{ + uint32_t scroll_methods; + char *str; + enum libinput_config_scroll_method method; + + scroll_methods = libinput_device_config_scroll_get_methods(device); + if (scroll_methods == LIBINPUT_CONFIG_SCROLL_NO_SCROLL) { + asprintf(&str, "none"); + return str; + } + + method = libinput_device_config_scroll_get_default_method(device); + + asprintf(&str, + "%s%s%s%s%s%s", + (method == LIBINPUT_CONFIG_SCROLL_2FG) ? "*" : "", + (scroll_methods & LIBINPUT_CONFIG_SCROLL_2FG) ? "two-finger " : "", + (method == LIBINPUT_CONFIG_SCROLL_EDGE) ? "*" : "", + (scroll_methods & LIBINPUT_CONFIG_SCROLL_EDGE) ? "edge " : "", + (method == LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN) ? "*" : "", + (scroll_methods & LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN) ? "button" : ""); + return str; +} + +static char* +click_defaults(struct libinput_device *device) +{ + uint32_t click_methods; + char *str; + enum libinput_config_click_method method; + + click_methods = libinput_device_config_click_get_methods(device); + if (click_methods == LIBINPUT_CONFIG_CLICK_METHOD_NONE) { + asprintf(&str, "none"); + return str; + } + + method = libinput_device_config_click_get_default_method(device); + asprintf(&str, + "%s%s%s%s", + (method == LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS) ? "*" : "", + (click_methods & LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS) ? "button-areas " : "", + (method == LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER) ? "*" : "", + (click_methods & LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER) ? "clickfinger " : ""); + return str; +} + +static void +print_device_notify(struct libinput_event *ev) +{ + struct libinput_device *dev = libinput_event_get_device(ev); + struct libinput_seat *seat = libinput_device_get_seat(dev); + struct libinput_device_group *group; + double w, h; + static int next_group_id = 0; + intptr_t group_id; + const char *devnode; + char *str; + + group = libinput_device_get_device_group(dev); + group_id = (intptr_t)libinput_device_group_get_user_data(group); + if (!group_id) { + group_id = ++next_group_id; + libinput_device_group_set_user_data(group, (void*)group_id); + } + + devnode = udev_device_get_devnode( + libinput_device_get_udev_device(dev)); + + printf("Device: %s\n" + "Kernel: %s\n" + "Group: %d\n" + "Seat: %s, %s\n", + libinput_device_get_name(dev), + devnode, + (int)group_id, + libinput_seat_get_physical_name(seat), + libinput_seat_get_logical_name(seat)); + + if (libinput_device_get_size(dev, &w, &h) == 0) + printf("Size: %.2fx%.2fmm\n", w, h); + printf("Capabilities: "); + if (libinput_device_has_capability(dev, + LIBINPUT_DEVICE_CAP_KEYBOARD)) + printf("keyboard "); + if (libinput_device_has_capability(dev, + LIBINPUT_DEVICE_CAP_POINTER)) + printf("pointer "); + if (libinput_device_has_capability(dev, + LIBINPUT_DEVICE_CAP_TOUCH)) + printf("touch"); + printf("\n"); + + printf("Tap-to-click: %s\n", tap_default(dev)); + printf("Left-handed: %s\n", left_handed_default(dev)); + printf("Nat.scrolling: %s\n", nat_scroll_default(dev)); + printf("Middle emulation: %s\n", middle_emulation_default(dev)); + str = calibration_default(dev); + printf("Calibration: %s\n", str); + free(str); + + str = scroll_defaults(dev); + printf("Scroll methods: %s\n", str); + free(str); + + str = click_defaults(dev); + printf("Click methods: %s\n", str); + free(str); + + printf("\n"); +} + +static inline void +usage(void) +{ + printf("Usage: %s [--help|--version]\n" + "\n" + "This tool creates a libinput context on the default seat \"seat0\"\n" + "and lists all devices recognized by libinput and the configuration options.\n" + "Where multiple options are possible, the default is prefixed with \"*\".\n" + "\n" + "Options:\n" + "--help ...... show this help\n" + "--version ... show version information\n" + "\n" + "This tool requires access to the /dev/input/eventX nodes.\n", + program_invocation_short_name); +} + +int +main(int argc, char **argv) +{ + struct libinput *li; + struct tools_options options; + struct libinput_event *ev; + + if (argc > 1) { + if (strcmp(argv[1], "--help") == 0) { + usage(); + return 0; + } else if (strcmp(argv[1], "--version") == 0) { + printf("%s\n", LIBINPUT_VERSION); + return 0; + } else { + usage(); + return 1; + } + } + + tools_init_options(&options); + + li = tools_open_backend(&options, NULL, &interface); + if (!li) + return 1; + + libinput_dispatch(li); + while ((ev = libinput_get_event(li))) { + + if (libinput_event_get_type(ev) == LIBINPUT_EVENT_DEVICE_ADDED) + print_device_notify(ev); + + libinput_event_destroy(ev); + libinput_dispatch(li); + } + + libinput_unref(li); + + return 0; +} diff --git a/tools/libinput-list-devices.man b/tools/libinput-list-devices.man new file mode 100644 index 00000000..cd89283a --- /dev/null +++ b/tools/libinput-list-devices.man @@ -0,0 +1,37 @@ +.TH LIBINPUT-LIST-DEVICES "1" +.SH NAME +libinput-list-devices \- list local devices as recognized by libinput +.SH SYNOPSIS +.B libinput-list-devices [--help] +.SH DESCRIPTION +.PP +The +.I libinput-list-devices +tool creates a libinput context on the default seat "seat0" and lists all +devices regonized by libinput. Each device shows available configurations +the respective default configuration setting. +.PP +For configuration options that allow multiple different settings (e.g. +scrolling), all available settings are listed. The default setting is +prefixed by an asterisk (*). +.PP +This tool usually needs to be run as root to have access to the +/dev/input/eventX nodes. +.SH OPTIONS +.TP 8 +.B --help +Print help +.SH NOTES +.PP +Some specific feature may still be available on a device even when +no configuration is exposed, a lack of a configuration option does not +necessarily mean that this feature does not work. +.PP +A device may be recognized by libinput but not handled by the X.Org libinput +driver or the Wayland compositor. +.PP +An xorg.conf(5) configuration entry or Wayland compositor setting may have +changed configurations on a device. The +.I libinput-list-devices +tool only shows the device's default configuration, not the current +configuration. diff --git a/tools/ptraccel-debug.c b/tools/ptraccel-debug.c new file mode 100644 index 00000000..fdd8490e --- /dev/null +++ b/tools/ptraccel-debug.c @@ -0,0 +1,300 @@ +/* + * Copyright © 2015 Red Hat, Inc. + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void +print_ptraccel_deltas(struct motion_filter *filter, double step) +{ + struct normalized_coords motion; + uint64_t time = 0; + double i; + + printf("# gnuplot:\n"); + printf("# set xlabel dx unaccelerated\n"); + printf("# set ylabel dx accelerated\n"); + printf("# set style data lines\n"); + printf("# plot \"gnuplot.data\" using 1:2 title \"step %.2f\"\n", step); + printf("#\n"); + + /* Accel flattens out after 15 and becomes linear */ + for (i = 0.0; i < 15.0; i += step) { + motion.x = i; + motion.y = 0; + time += 12; /* pretend 80Hz data */ + + motion = filter_dispatch(filter, &motion, NULL, time); + + printf("%.2f %.3f\n", i, motion.x); + } +} + +static void +print_ptraccel_movement(struct motion_filter *filter, + int nevents, + double max_dx, + double step) +{ + struct normalized_coords motion; + uint64_t time = 0; + double dx; + int i; + + printf("# gnuplot:\n"); + printf("# set xlabel \"event number\"\n"); + printf("# set ylabel \"delta motion\"\n"); + printf("# set style data lines\n"); + printf("# plot \"gnuplot.data\" using 1:2 title \"dx out\", \\\n"); + printf("# \"gnuplot.data\" using 1:3 title \"dx in\"\n"); + printf("#\n"); + + if (nevents == 0) { + if (step > 1.0) + nevents = max_dx; + else + nevents = 1.0 * max_dx/step + 0.5; + + /* Print more events than needed so we see the curve + * flattening out */ + nevents *= 1.5; + } + + dx = 0; + + for (i = 0; i < nevents; i++) { + motion.x = dx; + motion.y = 0; + time += 12; /* pretend 80Hz data */ + + filter_dispatch(filter, &motion, NULL, time); + + printf("%d %.3f %.3f\n", i, motion.x, dx); + + if (dx < max_dx) + dx += step; + } +} + +static void +print_ptraccel_sequence(struct motion_filter *filter, + int nevents, + double *deltas) +{ + struct normalized_coords motion; + uint64_t time = 0; + double *dx; + int i; + + printf("# gnuplot:\n"); + printf("# set xlabel \"event number\"\n"); + printf("# set ylabel \"delta motion\"\n"); + printf("# set style data lines\n"); + printf("# plot \"gnuplot.data\" using 1:2 title \"dx out\", \\\n"); + printf("# \"gnuplot.data\" using 1:3 title \"dx in\"\n"); + printf("#\n"); + + dx = deltas; + + for (i = 0; i < nevents; i++, dx++) { + motion.x = *dx; + motion.y = 0; + time += 12; /* pretend 80Hz data */ + + filter_dispatch(filter, &motion, NULL, time); + + printf("%d %.3f %.3f\n", i, motion.x, *dx); + } +} + +static void +print_accel_func(struct motion_filter *filter) +{ + double vel; + + printf("# gnuplot:\n"); + printf("# set xlabel \"speed\"\n"); + printf("# set ylabel \"raw accel factor\"\n"); + printf("# set style data lines\n"); + printf("# plot \"gnuplot.data\" using 1:2\n"); + for (vel = 0.0; vel < 3.0; vel += .0001) { + double result = pointer_accel_profile_linear(filter, + NULL, + vel, + 0 /* time */); + printf("%.4f\t%.4f\n", vel, result); + } +} + +static void +usage(void) +{ + printf("Usage: %s [options] [dx1] [dx2] [...] > gnuplot.data\n", program_invocation_short_name); + printf("\n" + "Options:\n" + "--mode= \n" + " motion ... print motion to accelerated motion (default)\n" + " delta ... print delta to accelerated delta\n" + " accel ... print accel factor\n" + " sequence ... print motion for custom delta sequence\n" + "--maxdx=\n ... in motion mode only. Stop increasing dx at maxdx\n" + "--steps=\n ... in motion and delta modes only. Increase dx by step each round\n" + "--speed=\n ... accel speed [-1, 1], default 0\n" + "\n" + "If extra arguments are present and mode is not given, mode defaults to 'sequence'\n" + "and the arguments are interpreted as sequence of delta x coordinates\n" + "\n" + "If stdin is a pipe, mode defaults to 'sequence' and the pipe is read \n" + "for delta coordinates\n" + "\n" + "Output best viewed with gnuplot. See output for gnuplot commands\n"); +} + +int +main(int argc, char **argv) +{ + struct motion_filter *filter; + double step = 0.1, + max_dx = 10; + int nevents = 0; + bool print_accel = false, + print_motion = true, + print_delta = false, + print_sequence = false; + double custom_deltas[1024]; + double speed = 0.0; + enum { + OPT_MODE = 1, + OPT_NEVENTS, + OPT_MAXDX, + OPT_STEP, + OPT_SPEED, + }; + + filter = create_pointer_accelerator_filter(pointer_accel_profile_linear); + assert(filter != NULL); + + while (1) { + int c; + int option_index = 0; + static struct option long_options[] = { + {"mode", 1, 0, OPT_MODE }, + {"nevents", 1, 0, OPT_NEVENTS }, + {"maxdx", 1, 0, OPT_MAXDX }, + {"step", 1, 0, OPT_STEP }, + {"speed", 1, 0, OPT_SPEED }, + {0, 0, 0, 0} + }; + + c = getopt_long(argc, argv, "", + long_options, &option_index); + if (c == -1) + break; + + switch (c) { + case OPT_MODE: + if (strcmp(optarg, "accel") == 0) + print_accel = true; + else if (strcmp(optarg, "motion") == 0) + print_motion = true; + else if (strcmp(optarg, "delta") == 0) + print_delta = true; + else if (strcmp(optarg, "sequence") == 0) + print_sequence = true; + else { + usage(); + return 1; + } + break; + case OPT_NEVENTS: + nevents = atoi(optarg); + if (nevents == 0) { + usage(); + return 1; + } + break; + case OPT_MAXDX: + max_dx = strtod(optarg, NULL); + if (max_dx == 0.0) { + usage(); + return 1; + } + break; + case OPT_STEP: + step = strtod(optarg, NULL); + if (step == 0.0) { + usage(); + return 1; + } + break; + case OPT_SPEED: + speed = strtod(optarg, NULL); + break; + default: + usage(); + exit(1); + break; + } + } + + filter_set_speed(filter, speed); + + if (!isatty(STDIN_FILENO)) { + char buf[12]; + print_sequence = true; + print_motion = false; + nevents = 0; + memset(custom_deltas, 0, sizeof(custom_deltas)); + + while(fgets(buf, sizeof(buf), stdin) && nevents < 1024) { + custom_deltas[nevents++] = strtod(buf, NULL); + } + } else if (optind < argc) { + print_sequence = true; + print_motion = false; + nevents = 0; + memset(custom_deltas, 0, sizeof(custom_deltas)); + while (optind < argc) + custom_deltas[nevents++] = strtod(argv[optind++], NULL); + } + + if (print_accel) + print_accel_func(filter); + else if (print_delta) + print_ptraccel_deltas(filter, step); + else if (print_motion) + print_ptraccel_movement(filter, nevents, max_dx, step); + else if (print_sequence) + print_ptraccel_sequence(filter, nevents, custom_deltas); + + filter_destroy(filter); + + return 0; +} diff --git a/tools/shared.c b/tools/shared.c index 2cff52c4..9ccd5dda 100644 --- a/tools/shared.c +++ b/tools/shared.c @@ -30,6 +30,8 @@ #include #include +#include + #include "shared.h" enum options { @@ -43,7 +45,12 @@ enum options { OPT_NATURAL_SCROLL_DISABLE, OPT_LEFT_HANDED_ENABLE, OPT_LEFT_HANDED_DISABLE, + OPT_MIDDLEBUTTON_ENABLE, + OPT_MIDDLEBUTTON_DISABLE, OPT_CLICK_METHOD, + OPT_SCROLL_METHOD, + OPT_SCROLL_BUTTON, + OPT_SPEED, }; static void @@ -70,7 +77,12 @@ tools_usage() "--disable-natural-scrolling.... enable/disable natural scrolling\n" "--enable-left-handed\n" "--disable-left-handed.... enable/disable left-handed button configuration\n" + "--enable-middlebutton\n" + "--disable-middlebutton.... enable/disable middle button emulation\n" "--set-click-method=[none|clickfinger|buttonareas] .... set the desired click method\n" + "--set-scroll-method=[none|twofinger|edge|button] ... set the desired scroll method\n" + "--set-scroll-button=BTN_MIDDLE ... set the button to the given button code\n" + "--set-speed=.... set pointer acceleration speed\n" "\n" "These options apply to all applicable devices, if a feature\n" "is not explicitly specified it is left at each device's default.\n" @@ -88,9 +100,13 @@ tools_init_options(struct tools_options *options) options->tapping = -1; options->natural_scroll = -1; options->left_handed = -1; + options->middlebutton = -1; options->click_method = -1; + options->scroll_method = -1; + options->scroll_button = -1; options->backend = BACKEND_UDEV; options->seat = "seat0"; + options->speed = 0.0; } int @@ -110,7 +126,12 @@ tools_parse_args(int argc, char **argv, struct tools_options *options) { "disable-natural-scrolling", 0, 0, OPT_NATURAL_SCROLL_DISABLE }, { "enable-left-handed", 0, 0, OPT_LEFT_HANDED_ENABLE }, { "disable-left-handed", 0, 0, OPT_LEFT_HANDED_DISABLE }, + { "enable-middlebutton", 0, 0, OPT_MIDDLEBUTTON_ENABLE }, + { "disable-middlebutton", 0, 0, OPT_MIDDLEBUTTON_DISABLE }, { "set-click-method", 1, 0, OPT_CLICK_METHOD }, + { "set-scroll-method", 1, 0, OPT_SCROLL_METHOD }, + { "set-scroll-button", 1, 0, OPT_SCROLL_BUTTON }, + { "speed", 1, 0, OPT_SPEED }, { 0, 0, 0, 0} }; @@ -157,6 +178,12 @@ tools_parse_args(int argc, char **argv, struct tools_options *options) case OPT_LEFT_HANDED_DISABLE: options->left_handed = 0; break; + case OPT_MIDDLEBUTTON_ENABLE: + options->middlebutton = 1; + break; + case OPT_MIDDLEBUTTON_DISABLE: + options->middlebutton = 0; + break; case OPT_CLICK_METHOD: if (!optarg) { tools_usage(); @@ -176,6 +203,50 @@ tools_parse_args(int argc, char **argv, struct tools_options *options) return 1; } break; + case OPT_SCROLL_METHOD: + if (!optarg) { + tools_usage(); + return 1; + } + if (strcmp(optarg, "none") == 0) { + options->scroll_method = + LIBINPUT_CONFIG_SCROLL_NO_SCROLL; + } else if (strcmp(optarg, "twofinger") == 0) { + options->scroll_method = + LIBINPUT_CONFIG_SCROLL_2FG; + } else if (strcmp(optarg, "edge") == 0) { + options->scroll_method = + LIBINPUT_CONFIG_SCROLL_EDGE; + } else if (strcmp(optarg, "button") == 0) { + options->scroll_method = + LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN; + } else { + tools_usage(); + return 1; + } + break; + case OPT_SCROLL_BUTTON: + if (!optarg) { + tools_usage(); + return 1; + } + options->scroll_button = + libevdev_event_code_from_name(EV_KEY, + optarg); + if (options->scroll_button == -1) { + fprintf(stderr, + "Invalid button %s\n", + optarg); + return 1; + } + break; + case OPT_SPEED: + if (!optarg) { + tools_usage(); + return 1; + } + options->speed = atof(optarg); + break; default: tools_usage(); return 1; @@ -286,7 +357,21 @@ tools_device_apply_config(struct libinput_device *device, options->natural_scroll); if (options->left_handed != -1) libinput_device_config_left_handed_set(device, options->left_handed); + if (options->middlebutton != -1) + libinput_device_config_middle_emulation_set_enabled(device, + options->middlebutton); if (options->click_method != -1) libinput_device_config_click_set_method(device, options->click_method); + + if (options->scroll_method != -1) + libinput_device_config_scroll_set_method(device, + options->scroll_method); + if (options->scroll_button != -1) + libinput_device_config_scroll_set_button(device, + options->scroll_button); + + if (libinput_device_config_accel_is_available(device)) + libinput_device_config_accel_set_speed(device, + options->speed); } diff --git a/tools/shared.h b/tools/shared.h index fcf748fd..a1aec462 100644 --- a/tools/shared.h +++ b/tools/shared.h @@ -39,7 +39,11 @@ struct tools_options { int tapping; int natural_scroll; int left_handed; + int middlebutton; enum libinput_config_click_method click_method; + enum libinput_config_scroll_method scroll_method; + int scroll_button; + double speed; }; void tools_init_options(struct tools_options *options); diff --git a/udev/90-libinput-model-quirks.hwdb b/udev/90-libinput-model-quirks.hwdb new file mode 100644 index 00000000..048e5cc6 --- /dev/null +++ b/udev/90-libinput-model-quirks.hwdb @@ -0,0 +1,66 @@ +# Do not edit this file, it will be overwritten on update +# +# This file contains hwdb matches for libinput model-specific quirks. +# The contents of this file are a contract between libinput, udev rules and +# the hwdb. +# IT IS NOT A STABLE API AND SUBJECT TO CHANGE AT ANY TIME + +# The lookup keys are composed in: +# 90-libinput-model-quirks.rules +# +# Match string formats: +# libinput: +# libinput:name::dmi: + +# +# Sort by brand, model + +########################################## +# Google +########################################## + +# The various chromebooks, info from modinfo chromeos_laptop, touchpad names +# extrapolated from the chromiumos touchad-tests repo +# https://chromium.googlesource.com/chromiumos/platform/touchpad-tests +libinput:name:Cypress APA Trackpad (cyapa):dmi:*pnFalco:pvr* +libinput:name:SynPS/2 Synaptics TouchPad:dmi:*pn*Mario*: +libinput:name:Cypress APA Trackpad (cyapa):dmi:*pn*Butterfly*: +libinput:name:Cypress APA Trackpad (cyapa):dmi:*pn*Peppy*: +libinput:name:SynPS/2 Synaptics TouchPad:dmi:*pn*ZGB*: +libinput:name:Cypress APA Trackpad (cyapa):dmi:*pn*Parrot*: +libinput:name:Cypress APA Trackpad (cyapa):dmi:*bvn*coreboot*:pn*Leon*: +libinput:name:Cypress APA Trackpad (cyapa):dmi:*bvn*coreboot*:pn*Falco*: +libinput:name:Cypress APA Trackpad (cyapa):dmi:*bvn*coreboot*:pn*Wolf*: +libinput:name:Cypress APA Trackpad (cyapa):dmi:*svn*GOOGLE*:pn*Link*: +libinput:name:SynPS/2 Synaptics TouchPad:dmi:*pn*Alex*: +libinput:name:Cypress APA Trackpad (cyapa):dmi:*svn*SAMSUNG*:pn*Lumpy*: +libinput:name:Atmel maXTouch Touchpad:dmi:*svn*GOOGLE*:pn*Samus*: + LIBINPUT_MODEL_CHROMEBOOK=1 + +########################################## +# LENOVO +########################################## + +# X230 (Tablet) +libinput:name:SynPS/2 Synaptics TouchPad:dmi:*svnLENOVO:*:pvrThinkPadX230* + LIBINPUT_MODEL_LENOVO_X230=1 + +########################################## +# System76 +########################################## + +# Bonobo Professional +libinput:name:SynPS/2 Synaptics TouchPad:dmi:*svnSystem76*pvrbonp5* + LIBINPUT_MODEL_SYSTEM76_BONOBO=1 + +# Clevo +libinput:name:SynPS/2 Synaptics TouchPad:dmi:*pnW740SU*rnW740SU* + LIBINPUT_MODEL_CLEVO_W740SU=1 + +# Galago Ultra Pro +libinput:name:SynPS/2 Synaptics TouchPad:dmi:*svnSystem76*pvrgalu1* + LIBINPUT_MODEL_SYSTEM76_GALAGO=1 + +# Kudu Professional +libinput:name:SynPS/2 Synaptics TouchPad:dmi:*svnSystem76*pvrkudp1* + LIBINPUT_MODEL_SYSTEM76_KUDU=1 diff --git a/udev/90-libinput-model-quirks.rules b/udev/90-libinput-model-quirks.rules new file mode 100644 index 00000000..4b988748 --- /dev/null +++ b/udev/90-libinput-model-quirks.rules @@ -0,0 +1,25 @@ +# Do not edit this file, it will be overwritten on update +# +# This file contains lookup rules for libinput model-specific quirks. +# The contents of this file are a contract between libinput, udev rules and +# the hwdb. +# IT IS NOT A STABLE API AND SUBJECT TO CHANGE AT ANY TIME +# +# The hwdb database is in: +# 90-libinput-model-quirks.hwdb + +ACTION!="add|change", GOTO="libinput_model_quirks_end" +KERNEL!="event*", GOTO="libinput_model_quirks_end" + +# hwdb matches: +# +# libinput: +IMPORT{builtin}="hwdb --subsystem=input --lookup-prefix=libinput:", \ + GOTO="libinput_model_quirks_end" + +# libinput:name::dmi: +KERNELS=="input*", \ + IMPORT{builtin}="hwdb 'libinput:name:$attr{name}:$attr{[dmi/id]modalias}'", \ + GOTO="libinput_model_quirks_end" + +LABEL="libinput_model_quirks_end" diff --git a/udev/Makefile.am b/udev/Makefile.am index 3691172c..7d19809d 100644 --- a/udev/Makefile.am +++ b/udev/Makefile.am @@ -6,4 +6,10 @@ libinput_device_group_CFLAGS = $(LIBUDEV_CFLAGS) $(GCC_CFLAGS) libinput_device_group_LDADD = $(LIBUDEV_LIBS) udev_rulesdir=$(UDEV_DIR)/rules.d -dist_udev_rules_DATA = 80-libinput-device-groups.rules +dist_udev_rules_DATA = \ + 80-libinput-device-groups.rules \ + 90-libinput-model-quirks.rules + +udev_hwdbdir=$(UDEV_DIR)/hwdb.d +dist_udev_hwdb_DATA = \ + 90-libinput-model-quirks.hwdb