From 9680cd1d028f3cd3f3037762228c0734ba73d0ba Mon Sep 17 00:00:00 2001 From: Packit Service Date: Nov 06 2020 06:22:02 +0000 Subject: libinput-1.16.3 base --- diff --git a/CODING_STYLE.md b/CODING_STYLE.md index 4fe97ed..02e5844 100644 --- a/CODING_STYLE.md +++ b/CODING_STYLE.md @@ -1,3 +1,4 @@ +# Coding style - Indentation in tabs, 8 characters wide, spaces after the tabs where vertical alignment is required (see below) @@ -133,3 +134,94 @@ if (foo) { - Use stdbool.h's bool for booleans within the library (instead of `int`). Exception: the public API uses int, not bool. + +# Git commit message requirements + +Our CI will check the commit messages for a few requirements. Below is the +list of what we expect from a git commit. + +## Commit message content + +A [good commit message](http://who-t.blogspot.com/2009/12/on-commit-messages.html) needs to +answer three questions: + +- Why is it necessary? It may fix a bug, it may add a feature, it may + improve performance, reliabilty, stability, or just be a change for the + sake of correctness. +- How does it address the issue? For short obvious patches this part can be + omitted, but it should be a high level description of what the approach + was. +- What effects does the patch have? (In addition to the obvious ones, this + may include benchmarks, side effects, etc.) + +These three questions establish the context for the actual code changes, put +reviewers and others into the frame of mind to look at the diff and check if +the approach chosen was correct. A good commit message also helps +maintainers to decide if a given patch is suitable for stable branches or +inclusion in a distribution. + +## Developer Certificate of Origin + +Your commit **must** be signed off with a line: +``` +Signed-off-by: +``` +By signing off, you indicate the [developer certificate of origin](https://developercertificate.org/). + +> By making a contribution to this project, I certify that: +> +> (a) The contribution was created in whole or in part by me and I +> have the right to submit it under the open source license +> indicated in the file; or +> +> (b) The contribution is based upon previous work that, to the best +> of my knowledge, is covered under an appropriate open source +> license and I have the right under that license to submit that +> work with modifications, whether created in whole or in part +> by me, under the same open source license (unless I am +> permitted to submit under a different license), as indicated +> in the file; or +> +> (c) The contribution was provided directly to me by some other +> person who certified (a), (b) or (c) and I have not modified +> it. +> +> (d) I understand and agree that this project and the contribution +> are public and that a record of the contribution (including all +> personal information I submit with it, including my sign-off) is +> maintained indefinitely and may be redistributed consistent with +> this project or the open source license(s) involved. + +## Commit message format + +The canonical git commit message format is: + +``` +one line as the subject line with a high-level note + +full explanation of the patch follows after an empty line. This explanation +can be multiple paragraphs and is largely free-form. Markdown is not +supported. + +You can include extra data where required like: +- benchmark one says 10s +- benchmark two says 12s + +Signed-off-by: +``` + +The subject line is the first thing everyone sees about this commit, so make +sure it's on point. + +## Commit message technical requirements + +- The commit message should use present tense (not past tense). Do write + "change foo to bar", not "changed foo to bar". +- The text width of the commit should be 78 chars or less, especially the + subject line. +- The author and signed-off-by must be your real name and email address. We + do not accept the default `@users.noreply` gitlab addresses. + ``` + git config --global user.name Your Name + git config --global user.email your@email + ``` diff --git a/README.md b/README.md index 9872a4c..e9f0e66 100644 --- a/README.md +++ b/README.md @@ -81,4 +81,4 @@ file for the full license information. About ----- -Documentation generated by from git commit [__GIT_VERSION__](https://gitlab.freedesktop.org/libinput/libinput/commit/__GIT_VERSION__) +Documentation generated from git commit [__GIT_VERSION__](https://gitlab.freedesktop.org/libinput/libinput/commit/__GIT_VERSION__) diff --git a/completion/zsh/_libinput b/completion/zsh/_libinput index 49179dc..c25fb7a 100644 --- a/completion/zsh/_libinput +++ b/completion/zsh/_libinput @@ -7,7 +7,9 @@ "list-devices:List all devices recognized by libinput" "debug-events:Print all events as seen by libinput" "debug-gui:Show a GUI to visualize libinput's events" + "debug-tablet:Show tablet axis and button values" "measure:Measure various properties of devices" + "analyze:Analyze device data" "record:Record the events from a device" "replay:Replay the events from a device" ) @@ -76,7 +78,7 @@ __all_seats() '--disable-middlebutton[Disable middle button emulation]' \ + '(dwt)' \ '--enable-dwt[Enable disable-while-typing]' \ - '--disable-dwt[Disable enable-while-typing]' + '--disable-dwt[Disable disable-while-typing]' } (( $+functions[_libinput_debug-gui] )) || _libinput_debug-gui() @@ -89,6 +91,15 @@ __all_seats() '--udev=[Listen for notifications on the given seat]:seat:_libinput_all_seats' } +(( $+functions[_libinput_debug-tablet] )) || _libinput_debug-tablet() +{ + _arguments \ + '--help[Show debug-tablet help and exit]' \ + '--device=[Use the given device with the path backend]:device:_files -W /dev/input/ -P /dev/input/' \ + '--udev=[Use the first tablet device on the given seat]:seat:_libinput_all_seats' +} + + (( $+functions[_libinput_measure] )) || _libinput_measure() { local curcontext=$curcontext state line ret=1 @@ -152,6 +163,33 @@ __all_seats() ':device:_files -W /dev/input/ -P /dev/input/' } +(( $+functions[_libinput_analyze] )) || _libinput_analyze() +{ + local curcontext=$curcontext state line ret=1 + local features + features=( + "per-slot-delta:analyze relative movement per touch per slot" + ) + + _arguments -C \ + '--help[Print help and exit]' \ + ':feature:->feature' \ + '*:: :->option-or-argument' + + case $state in + (feature) + _describe -t features 'feature' features + ;; + (option-or-argument) + curcontext=${curcontext%:*:*}:libinput-analyze-$words[1]: + if ! _call_function ret _libinput_analyze_$words[1]; then + _message "unknown feature: $words[1]" + fi + ;; + esac + return ret +} + (( $+functions[_libinput_record] )) || _libinput_record() { _arguments \ diff --git a/doc/api/libinput.doxygen.in b/doc/api/libinput.doxygen.in index 0dffbbd..a006dae 100644 --- a/doc/api/libinput.doxygen.in +++ b/doc/api/libinput.doxygen.in @@ -10,7 +10,6 @@ MAX_INITIALIZER_LINES = 0 WARNINGS = YES QUIET = YES INPUT = "@builddir@" -FILTER_PATTERNS = *.h *.dox IMAGE_PATH = "@builddir@" GENERATE_HTML = YES HTML_OUTPUT = api diff --git a/doc/api/mainpage.dox b/doc/api/mainpage.dox index d843050..d655373 100644 --- a/doc/api/mainpage.dox +++ b/doc/api/mainpage.dox @@ -130,6 +130,6 @@ release. @section About -Documentation generated by from git commit [__GIT_VERSION__](https://gitlab.freedesktop.org/libinput/libinput/commit/__GIT_VERSION__) +Documentation generated from git commit [__GIT_VERSION__](https://gitlab.freedesktop.org/libinput/libinput/commit/__GIT_VERSION__) */ diff --git a/doc/api/meson.build b/doc/api/meson.build index 7eac152..20b298d 100644 --- a/doc/api/meson.build +++ b/doc/api/meson.build @@ -37,7 +37,7 @@ mainpage = vcs_tag(command : ['git', 'log', '-1', '--format=%h'], src_doxygen = files( # source files - join_paths(meson.source_root(), 'src', 'libinput.h'), + '../../src/libinput.h', # style files 'style/header.html', 'style/footer.html', diff --git a/doc/touchpad-tap-state-machine.svg b/doc/touchpad-tap-state-machine.svg index 5dd1036..37ce354 100644 --- a/doc/touchpad-tap-state-machine.svg +++ b/doc/touchpad-tap-state-machine.svg @@ -1,1508 +1,3 @@ + - - - - - - - - - - - - - IDLE - - - - - TOUCH - - - - - first - - finger down - - - - - - - finger up - - - - - - - button 1 - - press - - - - - timeout - - - - - - - move > - - threshold - - - - - - - second - - finger down - - - - - - - TOUCH_2 - - - - - second - - finger up - - - - - - - button 2 - - press - - - - - move > - - threshold - - - - - timeout - - - - - - - - - button 1 - - release - - - - - button 2 - - release - - - - - - - - - - - TAPPED - - - - - timeout - - - - - - - first - - finger down - - - - - - - DRAGGING - - - - - first - - finger up - - - - - btn1 - - release - - - - - - - - - - - IDLE - - - - - third - - finger down - - - - - - - TOUCH_3 - - - - - - - button 3 - - press - - - - - button 3 - - release - - - - - - - move > - - threshold - - - - - - - IDLE - - - - - timeout - - - - - - - first - - finger up - - - - - - - IDLE - - - - - fourth - - finger down - - - - - - - - - DRAGGING_OR_DOUBLETAP - - - - - - - timeout - - - - - - - first - - finger up - - - - - - - button 1 - - release - - - - - button 1 - - press - - - - - btn1 - - release - - - - - - - second - - finger down - - - - - - - move > - - threshold - - - - - - - - - HOLD - - - - - first - - finger up - - - - - - - - - second - - finger down - - - - - - - - - TOUCH_2_HOLD - - - - - second - - finger up - - - - - - - first - - finger up - - - - - - - - - third - - finger down - - - - - - - - - - - TOUCH_3_HOLD - - - - - - - fourth - - finger down - - - - - DEAD - - - - - - - - - - - any finger up - - - - - fourth - - finger up - - - - - any finger up - - - - - - - - - yes - - - - - any finger up - - - - - - - - - - - - - IDLE - - - - - if finger - - count == 0 - - - - - - - - - - - second - - finger up - - - - - DRAGGING_2 - - - - - - - - - first - - finger up - - - - - - - - - - - second - - finger down - - - - - - - - - - - third - - finger down - - - - - - - btn1 - - release - - - - - - - phys - - button - - press - - - - - - - - - - - - - - - - - phys - - button - - press - - - - - - - button 1 - - release - - - - - - - - - - - - - - - DRAGGING_WAIT - - - - - timeout - - - - - - - - - - - first - - finger down - - - - - - - TOUCH_TOUCH - - - - - TOUCH_IDLE - - - - - - - - - - - - - TOUCH_DEAD - - - - - - - - - - - - - TOUCH_DEAD - - - - - - - - - - - TOUCH_IDLE - - - - - - - TOUCH_TOUCH - - - - - - - - - TOUCH_IDLE - - - - - - - TOUCH_IDLE - - - - - - - TOUCH_TOUCH - - - - - - - that finger - - TOUCH_IDLE - - - - - - - TOUCH_DEAD - - - - - - - - - - - that finger - - TOUCH_IDLE - - - - - - - - - no - - - - - TOUCH_TOUCH - - - - - - - TOUCH_IDLE - - - - - TOUCH_TOUCH - - - - - - - TOUCH_DEAD - - - - - - - - - TOUCH_IDLE - - - - - - - TOUCH_TOUCH - - - - - TOUCH_TOUCH - - - - - TOUCH_IDLE - - - - - - - TOUCH_IDLE - - - - - - - TOUCH_TOUCH - - - - - - - TOUCH_IDLE - - - - - - - TOUCH_TOUCH - - - - - - - that finger - - TOUCH_IDLE - - - - - - - TOUCH_DEAD - - - - - - - TOUCH_DEAD - - - - - TOUCH_DEAD - - - - - TOUCH_DEAD - - - - - - - TOUCH_DEAD - - - - - - - TOUCH_DEAD - - - - - - - that finger state == - - TOUCH_TOUCH - - - - - TOUCH_DEAD - - - - - - - TOUCH_DEAD - - - - - TOUCH_DEAD - - - - - first - - finger down - - - - - MULTITAP - - - - - - - - - timeout - - - - - - - - - IDLE - - - - - - - - - - - MULTITAP_DOWN - - - - - first - - finger up - - - - - - - timeout - - - - - second - - finger down - - - - - move > - - threshold - - - - - - - - - - - TOUCH_TOUCH - - - - - - - TOUCH_IDLE - - - - - phys - - button - - press - - - - - - - - - - - DRAGGING_OR_TAP - - - - - first - - finger up - - - - - - - timeout - - - - - - - - - move > - - threshold - - - - - - - - - - - - - - - TOUCH_IDLE - - - - - - - - - - - -
-
- drag lock
- enabled?
-
-
-
- - [Not supported by viewer] -
-
- - - - - -
-
- no
-
-
- - no -
-
- - - - - -
-
- yes
-
-
-
- - yes<br> -
-
- - - - thumb - - - - - - - TOUCH_DEAD - - - - - - - - - - - TOUCH_2_RELEASE - - - - - second - - finger up - - - - - - - timeout - - - - - - - move > - - threshold - - - - - - - - - - - - - first - - finger down - - - - - - - - - TOUCH_IDLE - - - - - - - first - - finger up - - - - - - - - - second - - finger down - - - - - - - TOUCH_DEAD - - - - - TOUCH_DEAD - - - - - - - - - - - - - -
-
- drag
- disabled?
-
-
-
- - drag<br>disabled?<br> -
-
- - - - - -
-
- no
-
-
- - no -
-
- - - - - -
-
- yes
-
-
- - yes -
-
- - - - palm - - - - - - - either finger - - palm - - - - - - - remaining - -  finger - - palm - - - - - - - any finger - - palm - - - - - - - - - that finger - - TOUCH_DEAD - - - - - - - that finger - - TOUCH_DEAD - - - - - - - - - - - palm - - - - - - - - - - - any finger - - palm - - - - - - - that finger - - TOUCH_DEAD - - - - - - - - - TOUCH_DEAD - - - - - - - - - palm - - - - - TOUCH_DEAD - - - - - - - - - - - any finger - - palm - - - - - - - - - that finger - - TOUCH_DEAD - - - - - - - either finger - - palm - - - - - - - that finger - - TOUCH_DEAD - - - - - - - - - palm - - - - - - - - - TOUCH_DEAD - - - - - - - - - any finger - - palm - - - - - - - that finger - - TOUCH_DEAD - - - - - - - - - - - palm - - - - - - - - - - - - - button 1 - - press - - - - - - - - - - - TOUCH_DEAD - - - - - - - - - - - - - btn1 - - release - - - - - - - MULTITAP_PALM - - - - - first - - finger down - - - - - - - TOUCH_TOUCH - - - - - - - - - timeout - - - - - - - - - phys - - button - - press - - - - - -
-
+IDLETOUCHfirstfinger downfinger upbutton 1presstimeoutmove > thresholdsecondfinger downTOUCH_2secondfinger upbutton 2pressmove > thresholdtimeoutbutton 1releasebutton 2releaseTAPPEDtimeoutfirstfinger downDRAGGINGfirstfinger upbtn1releaseIDLEthirdfinger downTOUCH_3button 3pressbutton 3releasemove > thresholdIDLEtimeoutfirstfinger upIDLEfourthfinger downDRAGGING_OR_DOUBLETAPtimeoutfirstfinger upbutton 1releasesecondfinger downmove > thresholdHOLDfirstfinger upsecondfinger downTOUCH_2_HOLDsecondfinger upfirstfinger upthirdfinger downTOUCH_3_HOLDfourthfinger downDEADany finger upfourthfinger upany finger upyesany finger upIDLEif fingercount == 0secondfinger upDRAGGING_2firstfinger upsecondfinger downthirdfinger downbtn1releasephysbuttonpressphysbuttonpressbutton 1releaseDRAGGING_WAITtimeoutfirstfinger downTOUCH_TOUCHTOUCH_IDLETOUCH_DEADTOUCH_DEADTOUCH_IDLETOUCH_TOUCHTOUCH_IDLETOUCH_IDLETOUCH_TOUCHthat fingerTOUCH_IDLETOUCH_DEADthat fingerTOUCH_IDLEnoTOUCH_TOUCHTOUCH_IDLETOUCH_TOUCHTOUCH_DEADTOUCH_IDLETOUCH_TOUCHTOUCH_TOUCHTOUCH_IDLETOUCH_IDLETOUCH_TOUCHTOUCH_IDLETOUCH_TOUCHthat fingerTOUCH_IDLETOUCH_DEADTOUCH_DEADTOUCH_DEADTOUCH_DEADTOUCH_DEADTOUCH_DEADthat finger state ==TOUCH_TOUCHTOUCH_DEADTOUCH_DEADTOUCH_DEADDRAGGING_OR_TAPfirstfinger uptimeoutmove > thresholdTOUCH_IDLE
drag lock
enabled?
[Not supported by viewer]
no
no
yes
yes<br>
thumbTOUCH_DEADTOUCH_2_RELEASEsecondfinger uptimeoutmove > thresholdfirstfinger downTOUCH_IDLEfirstfinger upsecondfinger downTOUCH_DEADTOUCH_DEAD
drag
disabled?
drag<br>disabled?<br>
no
no
yes
yes
palmeither fingerpalmremaining fingerpalmany fingerpalmthat fingerTOUCH_DEADthat fingerTOUCH_DEADpalmany fingerpalmthat fingerTOUCH_DEADTOUCH_DEADpalmTOUCH_DEADany fingerpalmthat fingerTOUCH_DEADeither fingerpalmthat fingerTOUCH_DEADpalmTOUCH_DEADany fingerpalmthat fingerTOUCH_DEADTOUCH_DEADmove > thresholdmove > thresholdbutton 1press
\ No newline at end of file diff --git a/doc/user/absolute-coordinate-ranges.rst b/doc/user/absolute-coordinate-ranges.rst index db61376..f587083 100644 --- a/doc/user/absolute-coordinate-ranges.rst +++ b/doc/user/absolute-coordinate-ranges.rst @@ -33,59 +33,83 @@ Measuring and fixing touchpad ranges To fix the touchpad you need to: #. measure the physical size of your touchpad in mm -#. run touchpad-edge-detector -#. trim the udev match rule to something sensible -#. replace the resolution with the calculated resolution based on physical settings +#. run the ``libinput measure touchpad-size`` tool +#. verify the hwdb entry provided by this tool #. test locally -#. send a patch to the systemd project +#. send a patch to the `systemd project `_. Detailed explanations are below. -`libevdev `_ provides a tool -called **touchpad-edge-detector** that allows measuring the touchpad's input -ranges. Run the tool as root against the device node of your touchpad device -and repeatedly move a finger around the whole outside area of the -touchpad. Then control+c the process and note the output. -An example output is below: +.. note:: ``libinput measure touchpad-size`` was introduced in libinput + 1.16. For earlier versions, use `libevdev `_'s + ``touchpad-edge-detector`` tool. +The ``libinput measure touchpad-size`` tool is an interactive tool. It must +be called with the physical dimensions of the touchpad in mm. In the example +below, we use 100mm wide and 55mm high. The tool will find the touchpad device +automatically. + :: - $> sudo touchpad-edge-detector /dev/input/event4 - Touchpad SynPS/2 Synaptics TouchPad on /dev/input/event4 - Move one finger around the touchpad to detect the actual edges - Kernel says: x [1024..3112], y [2024..4832] - Touchpad sends: x [2445..4252], y [3464..4071] + $> sudo libinput measure touchpad-size 100x55 + Using "Touchpad SynPS/2 Synaptics TouchPad": /dev/input/event4 + + Kernel specified touchpad size: 99.7x75.9mm + User specified touchpad size: 100.0x55.0mm + + Kernel axis range: x [1024..5112], y [2024..4832] + Detected axis range: x [ 0.. 0], y [ 0.. 0] + + Move one finger along all edges of the touchpad + until the detected axis range stops changing. - Touchpad size as listed by the kernel: 49x66mm - Calculate resolution as: - x axis: 2088/ - y axis: 2808/ + ... - Suggested udev rule: - # - evdev:name:SynPS/2 Synaptics TouchPad:dmi:bvnLENOVO:bvrGJET72WW(2.22):bd02/21/2014:svnLENOVO:pn20ARS25701:pvrThinkPadT440s:rvnLENOVO:rn20ARS25701:rvrSDK0E50512STD:cvnLENOVO:ct10:cvrNotAvailable:* - EVDEV_ABS_00=2445:4252: - EVDEV_ABS_01=3464:4071: - EVDEV_ABS_35=2445:4252: - EVDEV_ABS_36=3464:4071: +Move the finger around until the detected axis range matches the data sent +by the device. ``Ctrl+C`` terminates the tool and prints a +suggested hwdb entry. :: + ... + Kernel axis range: x [1024..5112], y [2024..4832] + ^C + Detected axis range: x [2072..4880], y [2159..4832] + Resolutions calculated based on user-specified size: x 28, y 49 units/mm + Suggested hwdb entry: + Note: the dmi modalias match is a guess based on your machine's modalias: + dmi:bvnLENOVO:bvrGJET72WW(2.22):bd02/21/2014:svnLENOVO:pn20ARS25701:pvrThinkPadT440s:rvnLENOVO:rn20ARS25701:rvrSDK0E50512STD:cvnLENOVO:ct10:cvrNotAvailable: + Please verify that this is the most sensible match and adjust if necessary. + -8<-------------------------- + # Laptop model description (e.g. Lenovo X1 Carbon 5th) + evdev:name:SynPS/2 Synaptics TouchPad:dmi:*svnLENOVO:*pvrThinkPadT440s* + EVDEV_ABS_00=2072:4880:28 + EVDEV_ABS_01=2159:4832:49 + EVDEV_ABS_35=2072:4880:28 + EVDEV_ABS_36=2159:4832:49 + -8<-------------------------- + Instructions on what to do with this snippet are in /usr/lib/udev/hwdb.d/60-evdev.hwdb -Note the discrepancy between the coordinate range the kernels advertises vs. -what the touchpad sends. -To fix the advertised ranges, the udev rule should be taken and trimmed -before being sent to the `systemd project `_. + +If there are discrepancies between the coordinate range the kernels +advertises and what what the touchpad sends, the hwdb entry should be added to the +``60-evdev.hwdb`` file provided by the `systemd project `_. An example commit can be found `here `_. -In most cases the match can and should be trimmed to the system vendor (svn) -and the product version (pvr), with everything else replaced by a wildcard -(*). In this case, a Lenovo T440s, a suitable match string would be: +The ``libinput measure touchpad-size`` tool attempts to provide the correct +dmi match but it does require user verification. + +In most cases the dmi match can and should be trimmed to the system vendor (``svn``) +and the product version (``pvr``) or product name (``pn``), with everything else +replaced by a wildcard (``*``). In the above case, the match string is: + :: evdev:name:SynPS/2 Synaptics TouchPad:dmi:*svnLENOVO:*pvrThinkPadT440s* +As a general rule: for Lenovo devices use ``pvr`` and for all others use +``pn``. .. note:: hwdb match strings only allow for alphanumeric ascii characters. Use a wildcard (* or ?, whichever appropriate) for special characters. @@ -95,19 +119,19 @@ The actual axis overrides are in the form: :: # axis number=min:max:resolution - EVDEV_ABS_00=2445:4252:42 + EVDEV_ABS_00=2072:4880:28 or, if the range is correct but the resolution is wrong :: # axis number=::resolution - EVDEV_ABS_00=::42 + EVDEV_ABS_00=::28 Note the leading single space. The axis numbers are in hex and can be found -in *linux/input-event-codes.h*. For touchpads ABS_X, ABS_Y, -ABS_MT_POSITION_X and ABS_MT_POSITION_Y are required. +in ``linux/input-event-codes.h``. For touchpads ``ABS_X``, ``ABS_Y``, +``ABS_MT_POSITION_X`` and ``ABS_MT_POSITION_Y`` are required. .. note:: The touchpad's ranges and/or resolution should only be fixed when there is a significant discrepancy. A few units do not make a diff --git a/doc/user/building.rst b/doc/user/building.rst index 5099526..cacea2f 100644 --- a/doc/user/building.rst +++ b/doc/user/building.rst @@ -121,7 +121,7 @@ overwriting manually installed files. - **Fedora 22** and later: ``sudo dnf reinstall libinput`` - **RHEL/CentOS/Fedora 21** and earlier: ``sudo yum reinstall libinput`` - **openSUSE**: ``sudo zypper install --force libinput10`` -- **Arch**: ``sudo packman -S libinput`` +- **Arch**: ``sudo pacman -S libinput`` .. _building_selinux: diff --git a/doc/user/conf.py.in b/doc/user/conf.py.in index 8ec0ac5..6059bc9 100644 --- a/doc/user/conf.py.in +++ b/doc/user/conf.py.in @@ -21,7 +21,7 @@ sys.path.insert(0, os.path.abspath('@BUILDDIR@')) # -- Project information ----------------------------------------------------- project = '@PROJECT_NAME@' -copyright = '2018, the libinput authors' +copyright = '2019, the libinput authors' author = 'the libinput authors' # The short X.Y version diff --git a/doc/user/contributing.rst b/doc/user/contributing.rst index 54d69e4..5272f44 100644 --- a/doc/user/contributing.rst +++ b/doc/user/contributing.rst @@ -5,18 +5,141 @@ Contributing to libinput ============================================================================== -Contributions to libinput are always welcome. Please see the steps below for -details on how to create merge requests, correct git formatting and other -topics: + +So you want to contribute to libinput? Great! We'd love to help you be a part +of our community. Here is some important information to help you. .. contents:: :local: -Questions regarding this process can be asked on ``#wayland-devel`` on -freenode or on the `wayland-devel@lists.freedesktop.org +------------------------------------------------------------------------------ +Code of Conduct +------------------------------------------------------------------------------ + +As a freedesktop.org project, libinput follows the `freedesktop.org +Contributor Covenant `_. + +Please conduct yourself in a respectful and civilised manner when +interacting with community members on mailing lists, IRC, or bug trackers. +The community represents the project as a whole, and abusive or bullying +behaviour is not tolerated by the project. + +------------------------------------------------------------------------------ +Contact +------------------------------------------------------------------------------ + +Questions can be asked on ``#wayland-devel`` on freenode or on the +`wayland-devel@lists.freedesktop.org `_ mailing list. +For IRC, ping user ``whot`` (Peter Hutterer, the libinput maintainer) though +note that he lives on UTC+10 and thus the rest of the world is out of sync +by default ;) + +For anything that appears to be device specific and/or related to a new +feature, just file `an issue in our issue tracker +`_. It's usually the +most efficient way to get answers. + +------------------------------------------------------------------------------ +What to work on? +------------------------------------------------------------------------------ + +If you don't already know what you want to improve or fix with libinput, +then a good way of finding something is to search for the ``help needed`` +tag in our `issue tracker `_. +These are issues that have been triaged to some degree and deemed to be a +possible future feature to libinput. + +.. note:: Some of these issue may require specific hardware to reproduce. + +Another good place to help out with is the documentation. For anything you +find in these pages that isn't clear enough please feel free to reword it +and add what is missing. + +------------------------------------------------------------------------------ +Getting the code +------------------------------------------------------------------------------ + +The :ref:`building_libinput` have all the details but the short solution +will be: + +:: + + $> git clone https://gitlab.freedesktop.org/libinput/libinput + $> cd libinput + $> meson --prefix=/usr builddir/ + $> ninja -C builddir/ + $> sudo ninja -C builddir/ install + +You can omit the last step if you only want to test locally. + +------------------------------------------------------------------------------ +Working on the code +------------------------------------------------------------------------------ + +libinput has a roughly three-parts architecture: + +- the front-end code which handles the ``libinput_some_function()`` API calls in ``libinput.c`` +- the generic evdev interface handling which maps those API calls to the + backend calls (``evdev.c``). +- there are device-specific backends which do most of the actual work - + ``evdev-mt-touchpad.c`` is the one for touchpads for example. + +In general, things that only affect the internal workings of a device only +get implemented in the device-specific backend. You only need to touch the +API when you are adding configuration options. For more details, please read +the :ref:`architecture` document. There's also a `blog post describing the +building blocks +`_ +that may help to understand how it all fits together. + +Documentation is in ``/doc/api`` for the doxygen-generated API documentation. +These are extracted from the libinput source code directly. The +documentation you're reading right now is in ``/doc/user`` and generated with +sphinx. Simply running ``ninja -C builddir`` will rebuild it and the final +product ends up in ``builddir/Documentation``. + +------------------------------------------------------------------------------ +Testing the code +------------------------------------------------------------------------------ + +libinput provides a bunch of :ref:`tools` to debug any changes - without +having to install libinput. + +The two most useful ones are :ref:`libinput debug-events +` and :ref:`libinput debug-gui `. +Both tools can be run from the build directory directly and are great for +quick test iterations:: + + $> sudo ./builddir/libinput-debug-events --verbose + $> sudo ./builddir/libinput-debug-gui --verbose + +The former provides purely textual output and is useful for verifying event +streams from buttons, etc. The latter is particularly useful when you are +trying to debug pointer movement or placement. ``libinput debug-gui`` will +also visualize the raw data from the device so you can compare pointer +behavior with what comes from the kernel. + +These tools create a new libinput context and will not affect your session's +behavior. Only once you've installed libinput and restarted your session +will your changes affect the X server/Wayland compositor. + +Once everything seems to be correct, it's time to run the +:ref:`test-suite`:: + + $> sudo ./builddir/libinput-test-suite + +This test suite can take test names etc. as arguments, have a look at +:ref:`test-suite` for more info. There are a bunch of other tests that are +run by the CI on merge requests, you can run those locally with :: + + $> sudo ninja -C builddir check + +So it always pays to run that before submitting. This will also run the code +through valgrind and pick up any memory leaks. + ------------------------------------------------------------------------------ Submitting Code ------------------------------------------------------------------------------ @@ -96,18 +219,24 @@ same file(s) as the patch being sent. Commit Messages ------------------------------------------------------------------------------ -Read `on commit messages `_ -as a general guideline on what commit messages should contain. +Commit messages **must** contain a **Signed-off-by** line with your name +and email address. An example is: :: + + A description of this commit, and it's great work. + + Signed-off-by: Claire Someone -Commit messages **should** contain a **Signed-off-by** line with your name -and email address. If you're not the patch's original author, you should +If you're not the patch's original author, you should also gather S-o-b's by them (and/or whomever gave the patch to you.) The significance of this is that it certifies that you created the patch, that it was created under an appropriate open source license, or provided to you under those terms. This lets us indicate a chain of responsibility for the -copyright status of the code. +copyright status of the code. An example is: :: -We won't reject patches that lack S-o-b, but it is strongly recommended. + A description of this commit, and it's great work. + + Signed-off-by: Claire Someone + Signed-off-by: Ferris Crab When you re-send patches, revised or not, it would be very good to document the changes compared to the previous revision in the commit message and/or the @@ -116,6 +245,10 @@ should evaluate whether they still apply and include them in the respective commit messages. Otherwise the tags may be lost, reviewers miss the credit they deserve, and the patches may cause redundant review effort. +For further reading, please see +`'on commit messages' `_ +as a general guideline on what commit messages should contain. + ------------------------------------------------------------------------------ Coding Style ------------------------------------------------------------------------------ @@ -160,14 +293,3 @@ web interface though, so we do recommend using this to go through the review process, even if you use other clients to track the list of available patches. ------------------------------------------------------------------------------- -Code of Conduct ------------------------------------------------------------------------------- - -As a freedesktop.org project, libinput follows the `freedesktop.org -Contributor Covenant `_. - -Please conduct yourself in a respectful and civilised manner when -interacting with community members on mailing lists, IRC, or bug trackers. -The community represents the project as a whole, and abusive or bullying -behaviour is not tolerated by the project. diff --git a/doc/user/development.rst b/doc/user/development.rst index adf5341..b96d365 100644 --- a/doc/user/development.rst +++ b/doc/user/development.rst @@ -46,7 +46,6 @@ Hacking on libinput .. toctree:: :maxdepth: 1 - contributing.rst architecture test-suite.rst pointer-acceleration.rst diff --git a/doc/user/faqs.rst b/doc/user/faqs.rst index a6f5dee..3a9595d 100644 --- a/doc/user/faqs.rst +++ b/doc/user/faqs.rst @@ -42,6 +42,18 @@ This is a symptom of an invalid trackpoint multiplier. These devices need pointer acceleration accordingly. See :ref:`trackpoint_range` for a detailed explanation. +.. _faq_pointer_acceleration: + +------------------------------------------------------------------------------ +Why is libinput's pointer acceleration worse than synaptics/evdev +------------------------------------------------------------------------------ + +This is a known problem affecting some devices and/or use-case but the exact +cause is still unknown. It may be a device-specific issue, it may be a bug +in libinput's acceleration code, it may be a disagreement about how pointer +acceleration should feel. Unfortunately this is something that affected +users need to investigate and analyze. + .. _faq_enable_tapping: ------------------------------------------------------------------------------ @@ -275,28 +287,28 @@ details on the hwdb and how to modify it locally. .. _faq_timer_offset: ------------------------------------------------------------------------------ -What causes the "timer offset negative" warning? +What causes the "your system is too slow" warning? ------------------------------------------------------------------------------ libinput relies on the caller to call **libinput_dispatch()** whenever data is -available on the epoll-fd. Doing so will process the state of all devices -and can trigger some timers to be set (e.g. palm detection, tap-to-click, -disable-while-typing, etc.). Internally, libinput's time offsets are always -based on the event time of the triggering event. +available. **libinput_dispatch()** will process the state of all devices, +including some time-sensitive features (e.g. palm detection, tap-to-click, +disable-while-typing, etc.). -For example, a touch event with time T may trigger a timer for the time T + -180ms. When setting a timer, libinput checks the wall clock time to ensure -that this time T + offset is still in the future. If not, the warning is -logged. +If the time between the event and the call to **libinput_dispatch()** +is excessive, those features may not work correctly. For example, a delay in +touch event processing may cause wrong or missing tap-to-click events or +a palm may not be detected correctly. When this warning appears, it simply means that too much time has passed -between the event occurring (and the epoll-fd triggering) and the current -time. In almost all cases this is an indication of the caller being -overloaded and not handling events as speedily as required. +between the event occurring and the current time. In almost all cases this +is an indication of the caller being overloaded and not handling events as +speedily as required. The warning has no immediate effect on libinput's behavior but some of the -functionality that relies on the timer may be impeded (e.g. palms are not -detected as they should be). +functionality that relies on the timer may be impeded. This is not a bug in +libinput. libinput does not control how quickly **libinput_dispatch()** is +called. .. _faq_wayland: @@ -312,14 +324,13 @@ direct connection. As a technical analogy, the question is similar to "is glibc required for HTTP", or (stretching the analogy a bit further) "Is a pen required to write English". No, it isn't. -You can use libinput without a Wayland compositor, you can -write a Wayland compositor without libinput. Until 2018 the most common use -of libinput is with the X.Org X server through the xf86-input-libinput -driver. As Wayland compositors become more commonplace they will eventually -overtake X. +You can use libinput without a Wayland compositor, you can write a Wayland +compositor without libinput. On most major distributions, libinput is the +standard input stack used with the X.Org X server through the +xf86-input-libinput driver. So why "for your use-case - probably"? All general-purpose Wayland -compositors use libinput for their input stack. Wayland compositors that +compositors use libinput for their input stack. Wayland compositors that are more specialized (e.g. in-vehicle infotainment or IVI) can handle input devices directly but the compositor you want to use on your desktop needs an input stack that is more complex. And right now, diff --git a/doc/user/index.rst b/doc/user/index.rst index aaa84d7..611e991 100644 --- a/doc/user/index.rst +++ b/doc/user/index.rst @@ -10,7 +10,9 @@ faqs reporting-bugs troubleshooting + contributing development + API documentation <@HTTP_DOC_LINK@/api/> ++++++++++++++++++++++++++++++ @@ -41,7 +43,7 @@ API documentation ----------------- The API documentation is available here: - http://wayland.freedesktop.org/libinput/doc/latest/api/ + https://wayland.freedesktop.org/libinput/doc/latest/api/ .. note:: This documentation is generally only needed by authors of Wayland compositors or other developers dealing with input events directly. diff --git a/doc/user/meson.build b/doc/user/meson.build index f9904f1..733e1b9 100644 --- a/doc/user/meson.build +++ b/doc/user/meson.build @@ -8,6 +8,7 @@ sphinx_config = configuration_data() sphinx_config.set('PROJECT_NAME', meson.project_name()) sphinx_config.set('PROJECT_VERSION', meson.project_version()) sphinx_config.set('BUILDDIR', meson.current_build_dir()) +sphinx_config.set('HTTP_DOC_LINK', doc_url) git_version_page = vcs_tag(command : ['git', 'log', '-1', '--format=%H'], fallback : 'unknown', @@ -137,7 +138,6 @@ src_rst = files( 'device-quirks.rst', 'faqs.rst', 'gestures.rst', - 'index.rst', 'middle-button-emulation.rst', 'normalization-of-relative-motion.rst', 'palm-detection.rst', @@ -183,6 +183,10 @@ foreach f : src_rst src_sphinx += [ sf ] endforeach +configure_file(input: 'index.rst', + output: 'index.rst', + configuration: sphinx_config) + # do not use -j, it breaks on Ubuntu sphinx_output_dir = 'Documentation' @@ -190,5 +194,6 @@ custom_target('sphinx', input : [ sphinx_conf_py, git_version_page ] + src_sphinx + dst_404s, output : [ sphinx_output_dir ], command : [ sphinx, '-q', '-b', 'html', + '-d', join_paths(meson.current_build_dir(), 'doctrees'), meson.current_build_dir(), sphinx_output_dir], build_by_default : true) diff --git a/doc/user/reporting-bugs.rst b/doc/user/reporting-bugs.rst index ab22c5c..d117e89 100644 --- a/doc/user/reporting-bugs.rst +++ b/doc/user/reporting-bugs.rst @@ -64,7 +64,11 @@ For the vast majority of bugs you should not take longer than 5 seconds or three interactions (clicks, touches, taps, ...) with the device to reproduce. If it takes longer than that, you can narrow it down further. -Once you can reproduce it, use the :ref:`libinput-debug-events` helper tool. +Once you can reproduce it, use the :ref:`libinput-debug-events` helper +tool:: + + $> libinput debug-events --verbose + The output is textual and can help identify whether the bug is in libinput at all. Note that any configuration options you have set must be specified on the commandline, see the :ref:`libinput-debug-events` @@ -342,3 +346,51 @@ However, if the regression is in behavior unrelated to the fix itself it is usually better to file a new bug to reduce the noise. For example, if a fix to improve tapping breaks two-finger scrolling behavior, you should file a new bug but reference the original bug. + +.. _reporting_bugs_tags: + +------------------------------------------------------------------------------ +Gitlab issue tracker tags +------------------------------------------------------------------------------ + +The gitlab issue tracker allows developers to add tags to bugs to classify +them. + +- **being worked on**: someone is currently working on this feature. This + tag is used for features that will take a long time to implement fully and + prevents others from having to duplicate the work. Do reach out and ask if + help and/or further testing is needed. +- **bug**: issue is confirmed to be a bug +- **cantfix**: for technical reasons, this bug cannot be fixed, or at least + it cannot be fixed in libinput. +- **enhancement**: this issue describes a future feature, not a bug. +- **help needed**: this issue requires someone outside the libinput core + developer team to implement it. It is unlikely to be implemented + without someone stepping up to do the work. If you do see this tag, do ask + for guidance on how to implement it. +- **hw issue**: an issue that affects a specific device and is a hardware + bug, not a software bug. Often these needs to be worked around in libinput + but there are cases where a hw issue ends up as *cantfix*. +- **janitor**: a cleanup task that does not substantially affect how + libinput works. These are usually good bugs for newcomers to start on. +- **kernel**: this issue is a kernel bug, not a libinput bug. Often closed + as *cantfix* of *wontfix* as we wait for the kernel to address the issue + instead. +- **needs triage**: bug has not yet been confirmed by a core developer. +- **not our bug**: the issue is in some other component of the stack and + needs to be addressed there. +- **please test**: a fix is available but not yet merged and should be + tested by the reporter or others affected by the issue. +- **quirk**: this is issue needs :ref:`device-quirks` to be fixed +- **regression**: the issue is a regression to previous versions of + libinput. These issues get priorities. +- **waiting on reporter**: some more information is required from the + reporter and the issue cannot be fixed until the issue has been provided. + Where a bug is left in this state for too long, the bug will be closed as + *cantfix*. +- **wontfix**: this issue will not get fixed. This tag is usually assigned + to feature requests that are outside the scope of libinput or would put an + unreasonable maintenance burdern on the maintainers. + +These tags are high-level categories only, always look for the comments in +the issue to get further details. diff --git a/doc/user/scrolling.rst b/doc/user/scrolling.rst index c828a3d..82b8876 100644 --- a/doc/user/scrolling.rst +++ b/doc/user/scrolling.rst @@ -52,9 +52,9 @@ vertically or horizontally. Vertical and horizontal two-finger scrolling -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 +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. @@ -117,6 +117,13 @@ 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). +If the scroll button lock is enabled (see +**libinput_device_config_scroll_set_button_lock()**), the button does not +need to be held down. Pressing and releasing the button once enables the +button lock, the button is now considered logically held down. Pressing and +releasing the button a second time logically releases the button. While the +button is logically held down, motion events are converted to scroll events. + .. _scroll_sources: ------------------------------------------------------------------------------ diff --git a/doc/user/tools.rst b/doc/user/tools.rst index d470085..602319a 100644 --- a/doc/user/tools.rst +++ b/doc/user/tools.rst @@ -21,6 +21,8 @@ The most common tools used are: see :ref:`here ` - ``libinput measure``: measure properties on a kernel device, see :ref:`here ` +- ``libinput analyze``: analyse event recordings from a kernel device, + see :ref:`here ` - ``libinput quirks``: show quirks assigned to a device, see :ref:`here ` @@ -302,6 +304,19 @@ thing and one thing only and their usage is highly specific to the tool. Please see the **libinput-measure(1)** man page for information about what tools are available and the man page for each respective tool. +.. _libinput-analyze: + +------------------------------------------------------------------------------ +Analyzing device events with libinput analyze +------------------------------------------------------------------------------ + +The ``libinput analyze`` tool is a multiplexer for various sub-tools that +can analyze input events previously recorded from a device. + +Please see the **libinput-analyze(1)** man page for information about what +tools are available and the man page for each respective too. + + .. _libinput-quirks: ------------------------------------------------------------------------------ diff --git a/doc/user/touchpad-jumping-cursors.rst b/doc/user/touchpad-jumping-cursors.rst index d09cd44..e1b065a 100644 --- a/doc/user/touchpad-jumping-cursors.rst +++ b/doc/user/touchpad-jumping-cursors.rst @@ -14,10 +14,13 @@ position. When libinput detects a cursor jump it prints a bug warning to the log with the text **"Touch jump detected and discarded."** and a link to this page. -In most cases, this is a bug in the kernel driver and to libinput it appears -that the touch point moves from its previous position. The pointer jump can -usually be seen in the :ref:`libinput record ` output for the device: +.. note:: This warning is ratelimited and will stop appearing after a few + times, even if the touchpad jumps continue. +In most cases, this is a bug in the firmware (or kernel driver) and to +libinput it appears that the touch point moves from its previous position. +The pointer jump can usually be seen in the :ref:`libinput record +` output for the device: :: @@ -50,9 +53,28 @@ usually be seen in the :ref:`libinput record ` output for the d In this recording, the pointer jumps from its position 3752/2216 to 1640/4681 within a single frame. On this particular touchpad, this would represent a physical move of almost 50mm. libinput detects some of these -jumps and discards the movement but otherwise continues as usual. However, -the bug should be fixed at the kernel level. +jumps and discards the movement but otherwise continues as usual. +If your only encounter with these jumps is the warning printed to the log, +libinput functions as intended. When you encounter the warning in the log, please generate a recording of your touchpad with :ref:`libinput record ` and file a bug. See :ref:`reporting_bugs` for more details. + +Note that it most cases, libinput cannot actually fix the issue. Filing a +bug is useful to figure out if there are other factors at play or whether +there are heuristics we can employ to reduce the impact. + +------------------------------------------------------------------------------ +AlpsPS/2 ALPS DualPoint TouchPad jumping to 4095/0 +------------------------------------------------------------------------------ + +A special case of pointer jumps happens on ``AlpsPS/2 ALPS DualPoint TouchPad`` +devices found in the Lenovo ThinkPad E465 and E550 and likely others with +the same touchpad hardware. On those devices, the touchpad occasionally +sends an event for the second finger to move to position 4095/0 before +moving back to the original position. libinput detects this movement and +removes it but depending on the interaction this may cause a smaller jump +later when the coordinates reset to the new position of the finger. + +Some more information is available in `Gitlab Issue #492 `__. diff --git a/doc/user/touchpad-pressure-debugging.rst b/doc/user/touchpad-pressure-debugging.rst index 7389e98..58837d3 100644 --- a/doc/user/touchpad-pressure-debugging.rst +++ b/doc/user/touchpad-pressure-debugging.rst @@ -38,27 +38,43 @@ statistics, including whether a touch is/was considered logically down. Example output of the tool is below: :: - $ sudo libinput measure touchpad-pressure - Ready for recording data. - Pressure range used: 8:10 - Palm pressure range used: 65535 - Place a single finger on the touchpad to measure pressure values. - Ctrl+C to exit -   - Sequence 1190 pressure: min: 39 max: 48 avg: 43 median: 44 tags: down - Sequence 1191 pressure: min: 49 max: 65 avg: 62 median: 64 tags: down - Sequence 1192 pressure: min: 40 max: 78 avg: 64 median: 66 tags: down - Sequence 1193 pressure: min: 36 max: 83 avg: 70 median: 73 tags: down - Sequence 1194 pressure: min: 43 max: 76 avg: 72 median: 74 tags: down - Touchpad pressure: 47 min: 47 max: 86 tags: down + $ sudo libinput measure touchpad-pressure + Using Synaptics TM2668-002: /dev/input/event21 + + This is an interactive tool + + Place a single finger on the touchpad to measure pressure values. + Check that: + - touches subjectively perceived as down are tagged as down + - touches with a thumb are tagged as thumb + - touches with a palm are tagged as palm + + If the touch states do not match the interaction, re-run + with --touch-thresholds=down:up using observed pressure values. + See --help for more options. + + Press Ctrl+C to exit + + +-------------------------------------------------------------------------------+ + | Thresh | 70 | 60 | 130 | 100 | | + +-------------------------------------------------------------------------------+ + | Touch | down | up | palm | thumb | min | max | p | avg | median | + +-------------------------------------------------------------------------------+ + | 178 | x | x | | | 75 | 75 | 0 | 75 | 75 | + | 179 | x | x | | | 35 | 88 | 0 | 77 | 81 | + | 180 | x | x | | x | 65 | 113 | 0 | 98 | 98 | + | 181 | x | x | | x | 50 | 101 | 0 | 86 | 90 | + | 182 | x | x | | | 40 | 80 | 0 | 66 | 70 | + | 183 | x | | | | 43 | 78 | 78 | | + ... The example output shows five completed touch sequences and one ongoing one. For each, the respective minimum and maximum pressure values are printed as -well as some statistics. The ``tags`` show that sequence was considered -logically down at some point. This is an interactive tool and its output may -change frequently. Refer to the libinput-measure-touchpad-pressure(1) man -page for more details. +well as some statistics. The ``down`` column show that each sequence was +considered logically down at some point, two of the sequences were considered +thumbs. This is an interactive tool and its output may change frequently. Refer +to the **libinput-measure-touchpad-pressure(1)** man page for more details. By default, this tool uses the :ref:`device-quirks` for the pressure range. To narrow down on the best values for your device, specify the 'logically down' @@ -157,7 +173,7 @@ The example output shows five completed touch sequences. For each, the respective minimum and maximum pressure values are printed as well as some statistics. The ``down`` and ``palm`` tags show that sequence was considered logically down or a palm at some point. This is an interactive tool and its -output may change frequently. Refer to the libinput-measure-touch-size(1) man +output may change frequently. Refer to the **libinput-measure-touch-size(1)** man page for more details. By default, this tool uses the :ref:`device-quirks` for the touch size range. To diff --git a/include/linux/freebsd/input-event-codes.h b/include/linux/freebsd/input-event-codes.h index 7f14d4a..0c2e27d 100644 --- a/include/linux/freebsd/input-event-codes.h +++ b/include/linux/freebsd/input-event-codes.h @@ -1,4 +1,4 @@ -/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */ /* * Input event codes * @@ -439,10 +439,12 @@ #define KEY_TITLE 0x171 #define KEY_SUBTITLE 0x172 #define KEY_ANGLE 0x173 -#define KEY_ZOOM 0x174 +#define KEY_FULL_SCREEN 0x174 /* AC View Toggle */ +#define KEY_ZOOM KEY_FULL_SCREEN #define KEY_MODE 0x175 #define KEY_KEYBOARD 0x176 -#define KEY_SCREEN 0x177 +#define KEY_ASPECT_RATIO 0x177 /* HUTRR37: Aspect */ +#define KEY_SCREEN KEY_ASPECT_RATIO #define KEY_PC 0x178 /* Media Select Computer */ #define KEY_TV 0x179 /* Media Select TV */ #define KEY_TV2 0x17a /* Media Select Cable */ @@ -604,6 +606,7 @@ #define KEY_SCREENSAVER 0x245 /* AL Screen Saver */ #define KEY_VOICECOMMAND 0x246 /* Listening Voice Command */ #define KEY_ASSISTANT 0x247 /* AL Context-aware desktop assistant */ +#define KEY_KBD_LAYOUT_NEXT 0x248 /* AC Next Keyboard Layout Select */ #define KEY_BRIGHTNESS_MIN 0x250 /* Set Brightness to Minimum */ #define KEY_BRIGHTNESS_MAX 0x251 /* Set Brightness to Maximum */ @@ -646,6 +649,86 @@ */ #define KEY_DATA 0x277 #define KEY_ONSCREEN_KEYBOARD 0x278 +/* Electronic privacy screen control */ +#define KEY_PRIVACY_SCREEN_TOGGLE 0x279 + +/* Select an area of screen to be copied */ +#define KEY_SELECTIVE_SCREENSHOT 0x27a + +/* + * Some keyboards have keys which do not have a defined meaning, these keys + * are intended to be programmed / bound to macros by the user. For most + * keyboards with these macro-keys the key-sequence to inject, or action to + * take, is all handled by software on the host side. So from the kernel's + * point of view these are just normal keys. + * + * The KEY_MACRO# codes below are intended for such keys, which may be labeled + * e.g. G1-G18, or S1 - S30. The KEY_MACRO# codes MUST NOT be used for keys + * where the marking on the key does indicate a defined meaning / purpose. + * + * The KEY_MACRO# codes MUST also NOT be used as fallback for when no existing + * KEY_FOO define matches the marking / purpose. In this case a new KEY_FOO + * define MUST be added. + */ +#define KEY_MACRO1 0x290 +#define KEY_MACRO2 0x291 +#define KEY_MACRO3 0x292 +#define KEY_MACRO4 0x293 +#define KEY_MACRO5 0x294 +#define KEY_MACRO6 0x295 +#define KEY_MACRO7 0x296 +#define KEY_MACRO8 0x297 +#define KEY_MACRO9 0x298 +#define KEY_MACRO10 0x299 +#define KEY_MACRO11 0x29a +#define KEY_MACRO12 0x29b +#define KEY_MACRO13 0x29c +#define KEY_MACRO14 0x29d +#define KEY_MACRO15 0x29e +#define KEY_MACRO16 0x29f +#define KEY_MACRO17 0x2a0 +#define KEY_MACRO18 0x2a1 +#define KEY_MACRO19 0x2a2 +#define KEY_MACRO20 0x2a3 +#define KEY_MACRO21 0x2a4 +#define KEY_MACRO22 0x2a5 +#define KEY_MACRO23 0x2a6 +#define KEY_MACRO24 0x2a7 +#define KEY_MACRO25 0x2a8 +#define KEY_MACRO26 0x2a9 +#define KEY_MACRO27 0x2aa +#define KEY_MACRO28 0x2ab +#define KEY_MACRO29 0x2ac +#define KEY_MACRO30 0x2ad + +/* + * Some keyboards with the macro-keys described above have some extra keys + * for controlling the host-side software responsible for the macro handling: + * -A macro recording start/stop key. Note that not all keyboards which emit + * KEY_MACRO_RECORD_START will also emit KEY_MACRO_RECORD_STOP if + * KEY_MACRO_RECORD_STOP is not advertised, then KEY_MACRO_RECORD_START + * should be interpreted as a recording start/stop toggle; + * -Keys for switching between different macro (pre)sets, either a key for + * cycling through the configured presets or keys to directly select a preset. + */ +#define KEY_MACRO_RECORD_START 0x2b0 +#define KEY_MACRO_RECORD_STOP 0x2b1 +#define KEY_MACRO_PRESET_CYCLE 0x2b2 +#define KEY_MACRO_PRESET1 0x2b3 +#define KEY_MACRO_PRESET2 0x2b4 +#define KEY_MACRO_PRESET3 0x2b5 + +/* + * Some keyboards have a buildin LCD panel where the contents are controlled + * by the host. Often these have a number of keys directly below the LCD + * intended for controlling a menu shown on the LCD. These keys often don't + * have any labeling so we just name them KEY_KBD_LCD_MENU# + */ +#define KEY_KBD_LCD_MENU1 0x2b8 +#define KEY_KBD_LCD_MENU2 0x2b9 +#define KEY_KBD_LCD_MENU3 0x2ba +#define KEY_KBD_LCD_MENU4 0x2bb +#define KEY_KBD_LCD_MENU5 0x2bc #define BTN_TRIGGER_HAPPY 0x2c0 #define BTN_TRIGGER_HAPPY1 0x2c0 @@ -805,7 +888,8 @@ #define SW_LINEIN_INSERT 0x0d /* set = inserted */ #define SW_MUTE_DEVICE 0x0e /* set = device disabled */ #define SW_PEN_INSERTED 0x0f /* set = pen inserted */ -#define SW_MAX 0x0f +#define SW_MACHINE_COVER 0x10 /* set = cover closed */ +#define SW_MAX 0x10 #define SW_CNT (SW_MAX+1) /* diff --git a/include/linux/linux/input-event-codes.h b/include/linux/linux/input-event-codes.h index 7f14d4a..0c2e27d 100644 --- a/include/linux/linux/input-event-codes.h +++ b/include/linux/linux/input-event-codes.h @@ -1,4 +1,4 @@ -/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */ /* * Input event codes * @@ -439,10 +439,12 @@ #define KEY_TITLE 0x171 #define KEY_SUBTITLE 0x172 #define KEY_ANGLE 0x173 -#define KEY_ZOOM 0x174 +#define KEY_FULL_SCREEN 0x174 /* AC View Toggle */ +#define KEY_ZOOM KEY_FULL_SCREEN #define KEY_MODE 0x175 #define KEY_KEYBOARD 0x176 -#define KEY_SCREEN 0x177 +#define KEY_ASPECT_RATIO 0x177 /* HUTRR37: Aspect */ +#define KEY_SCREEN KEY_ASPECT_RATIO #define KEY_PC 0x178 /* Media Select Computer */ #define KEY_TV 0x179 /* Media Select TV */ #define KEY_TV2 0x17a /* Media Select Cable */ @@ -604,6 +606,7 @@ #define KEY_SCREENSAVER 0x245 /* AL Screen Saver */ #define KEY_VOICECOMMAND 0x246 /* Listening Voice Command */ #define KEY_ASSISTANT 0x247 /* AL Context-aware desktop assistant */ +#define KEY_KBD_LAYOUT_NEXT 0x248 /* AC Next Keyboard Layout Select */ #define KEY_BRIGHTNESS_MIN 0x250 /* Set Brightness to Minimum */ #define KEY_BRIGHTNESS_MAX 0x251 /* Set Brightness to Maximum */ @@ -646,6 +649,86 @@ */ #define KEY_DATA 0x277 #define KEY_ONSCREEN_KEYBOARD 0x278 +/* Electronic privacy screen control */ +#define KEY_PRIVACY_SCREEN_TOGGLE 0x279 + +/* Select an area of screen to be copied */ +#define KEY_SELECTIVE_SCREENSHOT 0x27a + +/* + * Some keyboards have keys which do not have a defined meaning, these keys + * are intended to be programmed / bound to macros by the user. For most + * keyboards with these macro-keys the key-sequence to inject, or action to + * take, is all handled by software on the host side. So from the kernel's + * point of view these are just normal keys. + * + * The KEY_MACRO# codes below are intended for such keys, which may be labeled + * e.g. G1-G18, or S1 - S30. The KEY_MACRO# codes MUST NOT be used for keys + * where the marking on the key does indicate a defined meaning / purpose. + * + * The KEY_MACRO# codes MUST also NOT be used as fallback for when no existing + * KEY_FOO define matches the marking / purpose. In this case a new KEY_FOO + * define MUST be added. + */ +#define KEY_MACRO1 0x290 +#define KEY_MACRO2 0x291 +#define KEY_MACRO3 0x292 +#define KEY_MACRO4 0x293 +#define KEY_MACRO5 0x294 +#define KEY_MACRO6 0x295 +#define KEY_MACRO7 0x296 +#define KEY_MACRO8 0x297 +#define KEY_MACRO9 0x298 +#define KEY_MACRO10 0x299 +#define KEY_MACRO11 0x29a +#define KEY_MACRO12 0x29b +#define KEY_MACRO13 0x29c +#define KEY_MACRO14 0x29d +#define KEY_MACRO15 0x29e +#define KEY_MACRO16 0x29f +#define KEY_MACRO17 0x2a0 +#define KEY_MACRO18 0x2a1 +#define KEY_MACRO19 0x2a2 +#define KEY_MACRO20 0x2a3 +#define KEY_MACRO21 0x2a4 +#define KEY_MACRO22 0x2a5 +#define KEY_MACRO23 0x2a6 +#define KEY_MACRO24 0x2a7 +#define KEY_MACRO25 0x2a8 +#define KEY_MACRO26 0x2a9 +#define KEY_MACRO27 0x2aa +#define KEY_MACRO28 0x2ab +#define KEY_MACRO29 0x2ac +#define KEY_MACRO30 0x2ad + +/* + * Some keyboards with the macro-keys described above have some extra keys + * for controlling the host-side software responsible for the macro handling: + * -A macro recording start/stop key. Note that not all keyboards which emit + * KEY_MACRO_RECORD_START will also emit KEY_MACRO_RECORD_STOP if + * KEY_MACRO_RECORD_STOP is not advertised, then KEY_MACRO_RECORD_START + * should be interpreted as a recording start/stop toggle; + * -Keys for switching between different macro (pre)sets, either a key for + * cycling through the configured presets or keys to directly select a preset. + */ +#define KEY_MACRO_RECORD_START 0x2b0 +#define KEY_MACRO_RECORD_STOP 0x2b1 +#define KEY_MACRO_PRESET_CYCLE 0x2b2 +#define KEY_MACRO_PRESET1 0x2b3 +#define KEY_MACRO_PRESET2 0x2b4 +#define KEY_MACRO_PRESET3 0x2b5 + +/* + * Some keyboards have a buildin LCD panel where the contents are controlled + * by the host. Often these have a number of keys directly below the LCD + * intended for controlling a menu shown on the LCD. These keys often don't + * have any labeling so we just name them KEY_KBD_LCD_MENU# + */ +#define KEY_KBD_LCD_MENU1 0x2b8 +#define KEY_KBD_LCD_MENU2 0x2b9 +#define KEY_KBD_LCD_MENU3 0x2ba +#define KEY_KBD_LCD_MENU4 0x2bb +#define KEY_KBD_LCD_MENU5 0x2bc #define BTN_TRIGGER_HAPPY 0x2c0 #define BTN_TRIGGER_HAPPY1 0x2c0 @@ -805,7 +888,8 @@ #define SW_LINEIN_INSERT 0x0d /* set = inserted */ #define SW_MUTE_DEVICE 0x0e /* set = device disabled */ #define SW_PEN_INSERTED 0x0f /* set = pen inserted */ -#define SW_MAX 0x0f +#define SW_MACHINE_COVER 0x10 /* set = cover closed */ +#define SW_MAX 0x10 #define SW_CNT (SW_MAX+1) /* diff --git a/meson.build b/meson.build index 91bc273..c9b53a3 100644 --- a/meson.build +++ b/meson.build @@ -1,8 +1,8 @@ project('libinput', 'c', - version : '1.14.3', + version : '1.16.3', license : 'MIT/Expat', default_options : [ 'c_std=gnu99', 'warning_level=2' ], - meson_version : '>= 0.41.0') + meson_version : '>= 0.45.0') libinput_version = meson.project_version().split('.') @@ -12,9 +12,9 @@ dir_libexec = join_paths(get_option('prefix'), get_option('libexecdir'), 'li dir_lib = join_paths(get_option('prefix'), get_option('libdir')) dir_man1 = join_paths(get_option('prefix'), get_option('mandir'), 'man1') dir_system_udev = join_paths(get_option('prefix'), 'lib', 'udev') -dir_src_quirks = join_paths(meson.source_root(), 'quirks') -dir_src_test = join_paths(meson.source_root(), 'test') -dir_src = join_paths(meson.source_root(), 'src') +dir_src_quirks = join_paths(meson.current_source_dir(), 'quirks') +dir_src_test = join_paths(meson.current_source_dir(), 'test') +dir_src = join_paths(meson.current_source_dir(), 'src') dir_udev = get_option('udev-dir') if dir_udev == '' @@ -43,16 +43,25 @@ libinput_so_version = '@0@.@1@.@2@'.format((libinput_lt_c - libinput_lt_a), # Compiler setup cc = meson.get_compiler('c') -cppflags = ['-Wno-unused-parameter', '-g', '-fvisibility=hidden'] +cppflags = ['-Wno-unused-parameter', '-fvisibility=hidden'] cflags = cppflags + ['-Wmissing-prototypes', '-Wstrict-prototypes'] add_project_arguments(cflags, language : 'c') add_project_arguments(cppflags, language : 'cpp') # config.h config_h = configuration_data() + +doc_url_base = 'https://wayland.freedesktop.org/libinput/doc' +if libinput_version[2].to_int() >= 90 + doc_url = '@0@/latest/'.format(doc_url_base) +else + doc_url = '@0@/@1@/'.format(doc_url_base, meson.project_version()) +endif +config_h.set_quoted('HTTP_DOC_LINK', doc_url) + config_h.set('_GNU_SOURCE', '1') if get_option('buildtype') == 'debug' or get_option('buildtype') == 'debugoptimized' - config_h.set_quoted('MESON_BUILD_ROOT', meson.build_root()) + config_h.set_quoted('MESON_BUILD_ROOT', meson.current_build_dir()) else config_h.set_quoted('MESON_BUILD_ROOT', '') endif @@ -149,8 +158,16 @@ executable('libinput-device-group', include_directories : [includes_src, includes_include], install : true, install_dir : dir_udev_callouts) -executable('libinput-fuzz-override', - 'udev/libinput-fuzz-override.c', +executable('libinput-fuzz-extract', + 'udev/libinput-fuzz-extract.c', + 'src/util-strings.c', + 'src/util-prop-parsers.c', + dependencies : [dep_udev, dep_libevdev, dep_lm], + include_directories : [includes_src, includes_include], + install : true, + install_dir : dir_udev_callouts) +executable('libinput-fuzz-to-zero', + 'udev/libinput-fuzz-to-zero.c', dependencies : [dep_udev, dep_libevdev], include_directories : [includes_src, includes_include], install : true, @@ -168,7 +185,7 @@ configure_file(input : 'udev/90-libinput-fuzz-override.rules.in', configuration : udev_rules_config) litest_udev_rules_config = configuration_data() -litest_udev_rules_config.set('UDEV_TEST_PATH', meson.build_root() + '/') +litest_udev_rules_config.set('UDEV_TEST_PATH', meson.current_build_dir() + '/') litest_groups_rules_file = configure_file(input : 'udev/80-libinput-device-groups.rules.in', output : '80-libinput-device-groups-litest.rules', configuration : litest_udev_rules_config) @@ -218,9 +235,46 @@ else endif ############ libinput-util.a ############ + +# Basic compilation test to make sure the headers include and define all the +# necessary bits. +util_headers = [ + 'util-bits.h', + 'util-input-event.h', + 'util-list.h', + 'util-macros.h', + 'util-matrix.h', + 'util-prop-parsers.h', + 'util-ratelimit.h', + 'util-strings.h', + 'util-time.h', +] +foreach h: util_headers + c = configuration_data() + c.set_quoted('FILE', h) + testfile = configure_file(input : 'test/test-util-includes.c', + output : 'test-util-includes-@0@.c'.format(h), + configuration : c) + executable('test-build-@0@'.format(h), + testfile, join_paths(dir_src, h), + include_directories : [includes_src, includes_include], + install : false) +endforeach + src_libinput_util = [ - 'src/libinput-util.c', - 'src/libinput-util.h' + 'src/util-bits.h', + 'src/util-list.c', + 'src/util-list.h', + 'src/util-macros.h', + 'src/util-matrix.h', + 'src/util-ratelimit.c', + 'src/util-ratelimit.h', + 'src/util-strings.h', + 'src/util-strings.c', + 'src/util-time.h', + 'src/util-prop-parsers.h', + 'src/util-prop-parsers.c', + 'src/libinput-util.h', ] libinput_util = static_library('libinput-util', src_libinput_util, @@ -235,6 +289,7 @@ src_libfilter = [ 'src/filter-low-dpi.c', 'src/filter-mouse.c', 'src/filter-touchpad.c', + 'src/filter-touchpad-flat.c', 'src/filter-touchpad-x230.c', 'src/filter-tablet.c', 'src/filter-trackpoint.c', @@ -252,46 +307,11 @@ libinput_data_override_path = join_paths(dir_sysconf, 'local-overrides.quirks') config_h.set_quoted('LIBINPUT_QUIRKS_DIR', dir_data) config_h.set_quoted('LIBINPUT_QUIRKS_OVERRIDE_FILE', libinput_data_override_path) -quirks_data = [ - 'quirks/10-generic-keyboard.quirks', - 'quirks/10-generic-lid.quirks', - 'quirks/10-generic-trackball.quirks', - 'quirks/30-vendor-aiptek.quirks', - 'quirks/30-vendor-alps.quirks', - 'quirks/30-vendor-contour.quirks', - 'quirks/30-vendor-cypress.quirks', - 'quirks/30-vendor-elantech.quirks', - 'quirks/30-vendor-ibm.quirks', - 'quirks/30-vendor-kensington.quirks', - 'quirks/30-vendor-logitech.quirks', - 'quirks/30-vendor-microsoft.quirks', - 'quirks/30-vendor-razer.quirks', - 'quirks/30-vendor-synaptics.quirks', - 'quirks/30-vendor-vmware.quirks', - 'quirks/30-vendor-wacom.quirks', - 'quirks/50-system-acer.quirks', - 'quirks/50-system-apple.quirks', - 'quirks/50-system-asus.quirks', - 'quirks/50-system-chicony.quirks', - 'quirks/50-system-cyborg.quirks', - 'quirks/50-system-dell.quirks', - 'quirks/50-system-google.quirks', - 'quirks/50-system-hp.quirks', - 'quirks/50-system-lenovo.quirks', - 'quirks/50-system-system76.quirks', - 'quirks/50-system-toshiba.quirks', -] - -test('quirks-in-meson.build', - find_program('quirks/test-quirks-in-meson.build.sh'), - args : [meson.source_root()], - suite : ['all'] - ) - -config_h.set_quoted('LIBINPUT_QUIRKS_FILES', ':'.join(quirks_data)) config_h.set_quoted('LIBINPUT_QUIRKS_SRCDIR', dir_src_quirks) - -install_data(quirks_data, install_dir : dir_data) +install_subdir('quirks', + exclude_files: ['README.md'], + install_dir : dir_data, + strip_directory : true) src_libquirks = [ 'src/quirks.c', @@ -428,7 +448,10 @@ man_config.set('LIBINPUT_VERSION', meson.project_version()) man_config.set('LIBINPUT_DATA_DIR', dir_data) deps_tools = [ dep_tools_shared, dep_libinput ] -libinput_debug_events_sources = [ 'tools/libinput-debug-events.c' ] +libinput_debug_events_sources = [ + 'tools/libinput-debug-events.c', + libinput_version_h, +] executable('libinput-debug-events', libinput_debug_events_sources, dependencies : deps_tools, @@ -442,6 +465,20 @@ configure_file(input : 'tools/libinput-debug-events.man', install_dir : dir_man1, ) +libinput_debug_tablet_sources = [ 'tools/libinput-debug-tablet.c' ] +executable('libinput-debug-tablet', + libinput_debug_tablet_sources, + dependencies : deps_tools, + include_directories : [includes_src, includes_include], + install_dir : libinput_tool_path, + install : true) + +configure_file(input : 'tools/libinput-debug-tablet.man', + output : 'libinput-debug-tablet.1', + configuration : man_config, + install_dir : dir_man1, + ) + libinput_quirks_sources = [ 'tools/libinput-quirks.c' ] libinput_quirks = executable('libinput-quirks', libinput_quirks_sources, @@ -505,8 +542,24 @@ configure_file(input : 'tools/libinput-measure.man', install_dir : dir_man1, ) +libinput_analyze_sources = [ 'tools/libinput-analyze.c' ] +executable('libinput-analyze', + libinput_analyze_sources, + dependencies : deps_tools, + include_directories : [includes_src, includes_include], + install_dir : libinput_tool_path, + install : true, + ) +configure_file(input : 'tools/libinput-analyze.man', + output : 'libinput-analyze.1', + configuration : man_config, + install_dir : dir_man1, + ) + src_python_tools = files( + 'tools/libinput-analyze-per-slot-delta.py', 'tools/libinput-measure-fuzz.py', + 'tools/libinput-measure-touchpad-size.py', 'tools/libinput-measure-touchpad-tap.py', 'tools/libinput-measure-touchpad-pressure.py', 'tools/libinput-measure-touch-size.py', @@ -529,9 +582,11 @@ endforeach src_man = files( 'tools/libinput-measure-fuzz.man', + 'tools/libinput-measure-touchpad-size.man', 'tools/libinput-measure-touchpad-tap.man', 'tools/libinput-measure-touchpad-pressure.man', 'tools/libinput-measure-touch-size.man', + 'tools/libinput-analyze-per-slot-delta.man', ) foreach m : src_man @@ -614,13 +669,16 @@ executable('ptraccel-debug', # subtool lookup if get_option('buildtype') == 'debug' or get_option('buildtype') == 'debugoptimized' config_tool_option_test = configuration_data() + config_tool_option_test.set('DISABLE_WARNING', 'yes') config_tool_option_test.set('MESON_ENABLED_DEBUG_GUI', get_option('debug-gui')) - tool_option_test = configure_file(input: 'tools/test-tool-option-parsing.py', - output: '@BASENAME@', + config_tool_option_test.set('MESON_BUILD_ROOT', meson.current_build_dir()) + config_tool_option_test.set('TOOL_PATH', libinput_tool.full_path()) + tool_option_test = configure_file(input: 'tools/test_tool_option_parsing.py', + output: '@PLAINNAME@', configuration : config_tool_option_test) test('tool-option-parsing', tool_option_test, - args : ['--tool-path', libinput_tool.full_path()], + args : [tool_option_test, '-n', 'auto'], suite : ['all', 'root'], timeout : 240) endif @@ -640,7 +698,7 @@ test('tools-builddir-lookup', test('tools-builddir-lookup-installed', find_program('test/helper-copy-and-exec-from-tmp.sh'), args : [test_builddir_lookup.full_path(), '--builddir-is-null'], - env : ['LD_LIBRARY_PATH=@0@'.format(meson.build_root())], + env : ['LD_LIBRARY_PATH=@0@'.format(meson.current_build_dir())], suite : ['all'], workdir : '/tmp') @@ -695,9 +753,11 @@ if get_option('tests') litest_sources = [ 'test/litest.h', 'test/litest-int.h', + 'test/litest-device-absinfo-override.c', 'test/litest-device-acer-hawaii-keyboard.c', 'test/litest-device-acer-hawaii-touchpad.c', 'test/litest-device-aiptek-tablet.c', + 'test/litest-device-alps-3fg.c', 'test/litest-device-alps-semi-mt.c', 'test/litest-device-alps-dualpoint.c', 'test/litest-device-anker-mouse-kbd.c', @@ -712,6 +772,7 @@ if get_option('tests') 'test/litest-device-dell-canvas-totem.c', 'test/litest-device-dell-canvas-totem-touch.c', 'test/litest-device-elantech-touchpad.c', + 'test/litest-device-elan-tablet.c', 'test/litest-device-generic-singletouch.c', 'test/litest-device-gpio-keys.c', 'test/litest-device-huion-pentablet.c', @@ -724,6 +785,7 @@ if get_option('tests') 'test/litest-device-keyboard-razer-blade-stealth-videoswitch.c', 'test/litest-device-lid-switch.c', 'test/litest-device-lid-switch-surface3.c', + 'test/litest-device-logitech-media-keyboard-elite.c', 'test/litest-device-logitech-trackball.c', 'test/litest-device-nexus4-touch-screen.c', 'test/litest-device-magic-trackpad.c', @@ -737,6 +799,7 @@ if get_option('tests') 'test/litest-device-ms-surface-cover.c', 'test/litest-device-protocol-a-touch-screen.c', 'test/litest-device-qemu-usb-tablet.c', + 'test/litest-device-sony-vaio-keys.c', 'test/litest-device-synaptics-x220.c', 'test/litest-device-synaptics-hover.c', 'test/litest-device-synaptics-i2c.c', @@ -744,6 +807,7 @@ if get_option('tests') 'test/litest-device-synaptics-st.c', 'test/litest-device-synaptics-t440.c', 'test/litest-device-synaptics-x1-carbon-3rd.c', + 'test/litest-device-tablet-mode-switch.c', 'test/litest-device-thinkpad-extrabuttons.c', 'test/litest-device-trackpoint.c', 'test/litest-device-touch-screen.c', @@ -797,10 +861,10 @@ if get_option('tests') litest_config_h = configuration_data() litest_config_h.set_quoted('LIBINPUT_DEVICE_GROUPS_RULES_FILE', - join_paths(meson.build_root(), + join_paths(meson.current_build_dir(), '80-libinput-device-groups-litest.rules')) litest_config_h.set_quoted('LIBINPUT_FUZZ_OVERRIDE_UDEV_RULES_FILE', - join_paths(meson.build_root(), + join_paths(meson.current_build_dir(), '90-libinput-fuzz-override-litest.rules')) def_no_main = '-DLITEST_NO_MAIN' @@ -836,7 +900,6 @@ if get_option('tests') test_utils_sources = [ 'src/libinput-util.h', - 'src/libinput-util.c', 'test/test-utils.c', ] test_utils = executable('test-utils', @@ -850,7 +913,6 @@ if get_option('tests') libinput_test_runner_sources = litest_sources + [ 'src/libinput-util.h', - 'src/libinput-util.c', 'test/test-udev.c', 'test/test-path.c', 'test/test-pointer.c', @@ -895,7 +957,8 @@ if get_option('tests') test('libinput-test-suite-@0@'.format(group), libinput_test_runner, suite : ['all', 'valgrind', 'root', 'hardware'], - args : ['--filter-group=@0@:*'.format(group)], + args : ['--filter-group=@0@:*'.format(group), + '--xml-output=junit-@0@-XXXXXX.xml'.format(group)], is_parallel : false, timeout : 1200) endforeach @@ -903,12 +966,12 @@ if get_option('tests') test('libinput-test-deviceless', libinput_test_runner, suite : ['all', 'valgrind'], - args: ['--filter-deviceless']) + args: ['--filter-deviceless', + '--xml-output=junit-deviceless-XXXXXX.xml']) valgrind = find_program('valgrind', required : false) if valgrind.found() valgrind_env = environment() - valgrind_env.set('LITEST_JOBS', '4') valgrind_suppressions_file = join_paths(dir_src_test, 'valgrind.suppressions') add_test_setup('valgrind', exe_wrapper : [ valgrind, diff --git a/quirks/30-vendor-aiptek.quirks b/quirks/30-vendor-aiptek.quirks index 23194e0..3efddb6 100644 --- a/quirks/30-vendor-aiptek.quirks +++ b/quirks/30-vendor-aiptek.quirks @@ -5,3 +5,10 @@ MatchUdevType=tablet MatchBus=usb MatchVendor=0x08CA AttrEventCodeDisable=ABS_TILT_X;ABS_TILT_Y; + +[Aiptek 8000U pressure threshold] +MatchUdevType=tablet +MatchBus=usb +MatchVendor=0x08CA +MatchProduct=0x0010 +AttrPressureRange=70:50 diff --git a/quirks/30-vendor-alps.quirks b/quirks/30-vendor-alps.quirks index e4e5d47..abdf98e 100644 --- a/quirks/30-vendor-alps.quirks +++ b/quirks/30-vendor-alps.quirks @@ -17,7 +17,13 @@ MatchUdevType=touchpad MatchBus=ps2 MatchVendor=0x0002 MatchProduct=0x0008 -ModelALPSTouchpad=1 +ModelALPSSerialTouchpad=1 + +[ALPS i2c Touchpads] +MatchUdevType=touchpad +MatchBus=i2c +MatchVendor=0x0488 +AttrPalmPressureThreshold=180 [ALPS v8 Touchpads] MatchUdevType=touchpad diff --git a/quirks/30-vendor-contour.quirks b/quirks/30-vendor-contour.quirks index a880f85..52cf7b6 100644 --- a/quirks/30-vendor-contour.quirks +++ b/quirks/30-vendor-contour.quirks @@ -4,6 +4,12 @@ MatchProduct=0x0401 MatchUdevType=mouse ModelBouncingKeys=1 +[Contour Design RollerMouse Free 3] +MatchVendor=0x0B33 +MatchProduct=0x0404 +MatchUdevType=mouse +ModelBouncingKeys=1 + [Contour Design RollerMouse Re:d] MatchVendor=0x0B33 MatchProduct=0x1000 diff --git a/quirks/30-vendor-logitech.quirks b/quirks/30-vendor-logitech.quirks index 23b6c88..c2b327d 100644 --- a/quirks/30-vendor-logitech.quirks +++ b/quirks/30-vendor-logitech.quirks @@ -46,11 +46,44 @@ MatchVendor=0x046D MatchProduct=0x4011 AttrPalmPressureThreshold=400 +[Logitech MX Master] +MatchVendor=0x46D +MatchProduct=0x4041 +ModelInvertHorizontalScrolling=1 +[Logitech MX Master] +MatchVendor=0x46D +MatchProduct=0x4060 +ModelInvertHorizontalScrolling=1 +[Logitech MX Master] +MatchVendor=0x46D +MatchProduct=0x4071 +ModelInvertHorizontalScrolling=1 + +# MX Master has a different PID on bluetooth +[Logitech MX Master] +MatchVendor=0x46D +MatchProduct=0xB012 +ModelInvertHorizontalScrolling=1 +[Logitech MX Master] +MatchVendor=0x46D +MatchProduct=0xB017 +ModelInvertHorizontalScrolling=1 +[Logitech MX Master] +MatchVendor=0x46D +MatchProduct=0xB01E +ModelInvertHorizontalScrolling=1 + [Logitech MX Master 2S] MatchVendor=0x46D MatchProduct=0x4069 ModelInvertHorizontalScrolling=1 +# MX Master 2S has a different PID on bluetooth +[Logitech MX Master 2S] +MatchVendor=0x46D +MatchProduct=0xB019 +ModelInvertHorizontalScrolling=1 + [Logitech MX Master 3] MatchVendor=0x46D MatchProduct=0x4082 diff --git a/quirks/30-vendor-madcatz.quirks b/quirks/30-vendor-madcatz.quirks new file mode 100644 index 0000000..57934c0 --- /dev/null +++ b/quirks/30-vendor-madcatz.quirks @@ -0,0 +1,40 @@ +# Do not edit this file, it will be overwritten on update + +# The Madcatz RAT3 has a mode button that cycles through event codes. +# On press, we get a release for the current mode and a press for the +# next mode: +# -event21 DEVICE_ADDED Madcatz Mad Catz R.A.T.3 Mouse seat0 default group1 cap:p left scroll-nat scroll-button +# event21 POINTER_BUTTON +2.35s BTN_BACK (278) pressed, seat count: 1 +# event21 POINTER_BUTTON +3.08s BTN_BACK (278) released, seat count: 0 +# event21 POINTER_BUTTON +3.08s BTN_TASK (279) pressed, seat count: 1 +# event21 POINTER_BUTTON +6.69s BTN_FORWARD (277) pressed, seat count: 1 +# event21 POINTER_BUTTON +6.69s BTN_TASK (279) released, seat count: 0 +# event21 POINTER_BUTTON +7.32s BTN_FORWARD (277) released, seat count: 0 +# event21 POINTER_BUTTON +7.32s BTN_BACK (278) pressed, seat count: 1 +# event21 POINTER_BUTTON +7.84s BTN_BACK (278) released, seat count: 0 +# event21 POINTER_BUTTON +7.84s BTN_TASK (279) pressed, seat count: 1 +# +# Disable the event codes to avoid stuck buttons. +[Madcatz RAT3] +MatchUdevType=mouse +MatchBus=usb +MatchVendor=0x0738 +MatchProduct=0x1703 +# EV_KEY 0x115, 0x116, 0x117 +AttrEventCodeDisable=EV_KEY:0x115;EV_KEY:0x116;EV_KEY:0x117 + +# Like the Madcatz RAT3, but with different codes: +# event8 POINTER_BUTTON +0.488s ??? (280) pressed, seat count: 1 +# event8 POINTER_BUTTON +1.275s ??? (280) released, seat count: 0 +# event8 POINTER_BUTTON +1.275s ??? (281) pressed, seat count: 1 +# event8 POINTER_BUTTON +3.585s ??? (281) released, seat count: 0 +# event8 POINTER_BUTTON +3.585s ??? (282) pressed, seat count: 1 +# event8 POINTER_BUTTON +4.184s ??? (280) pressed, seat count: 1 +# event8 POINTER_BUTTON +4.184s ??? (282) released, seat count: 0 +[Madcatz RAT7] +MatchUdevType=mouse +MatchBus=usb +MatchVendor=0x0738 +MatchProduct=0x1708 +# EV_KEY 0x118, 0x119, 0x11A +AttrEventCodeDisable=EV_KEY:0x118;EV_KEY:0x119;EV_KEY:0x11A diff --git a/quirks/30-vendor-razer.quirks b/quirks/30-vendor-razer.quirks index b3cd817..a5e4fc4 100644 --- a/quirks/30-vendor-razer.quirks +++ b/quirks/30-vendor-razer.quirks @@ -7,6 +7,13 @@ MatchVendor=0x1532 MatchProduct=0x0220 AttrKeyboardIntegration=internal +[Razer Blade Keyboard] +MatchUdevType=keyboard +MatchBus=usb +MatchVendor=0x1532 +MatchProduct=0x0233 +AttrKeyboardIntegration=internal + [Razer Blade Lid Switch] MatchName=*Lid Switch* MatchDMIModalias=dmi:*svnRazer:pnBlade* diff --git a/quirks/30-vendor-trust.quirks b/quirks/30-vendor-trust.quirks new file mode 100644 index 0000000..91d31b0 --- /dev/null +++ b/quirks/30-vendor-trust.quirks @@ -0,0 +1,6 @@ +[Trust GXT 25 Gaming Mouse] +MatchUdevType=mouse +MatchBus=usb +MatchVendor=0x1D57 +MatchProduct=0xAD03 +ModelBouncingKeys=1 diff --git a/quirks/30-vendor-wacom.quirks b/quirks/30-vendor-wacom.quirks index 73ccf2f..4274811 100644 --- a/quirks/30-vendor-wacom.quirks +++ b/quirks/30-vendor-wacom.quirks @@ -18,5 +18,4 @@ MatchUdevType=tablet MatchBus=usb MatchVendor=0x56A MatchProduct=0x4200 -ModelWacomISDV4Pen=1 AttrEventCodeDisable=ABS_TILT_X;ABS_TILT_Y; diff --git a/quirks/50-system-apple.quirks b/quirks/50-system-apple.quirks index b104f0a..3d42c8c 100644 --- a/quirks/50-system-apple.quirks +++ b/quirks/50-system-apple.quirks @@ -37,6 +37,9 @@ MatchVendor=0x05AC MatchProduct=0x030D AttrEventCodeDisable=EV_ABS +# The External Apple "Magic" trackpads, both the 1st and 2nd generations, have +# pretty good built-in spurious touch filtering in the device firmware. Using +# low enough values such as 20:10 effectively disables libinput's filtering. [Apple Magic Trackpad v1 (2010, clickpad)] MatchUdevType=touchpad MatchBus=bluetooth @@ -47,6 +50,25 @@ AttrTouchSizeRange=20:10 AttrPalmSizeThreshold=900 AttrThumbSizeThreshold=700 +# 2nd generation trackpad can be connected over Bluetooth as well as USB. +[Apple Magic Trackpad v2 (2015)] +MatchVendor=0x05AC +MatchProduct=0x0265 +AttrSizeHint=162x115 +AttrTouchSizeRange=20:10 +AttrPalmSizeThreshold=900 +AttrThumbSizeThreshold=800 +AttrPalmPressureThreshold=190 + +[Apple Magic Trackpad v2 (new vendor ID)] +MatchVendor=0x004C +MatchProduct=0x0265 +AttrSizeHint=162x115 +AttrTouchSizeRange=20:10 +AttrPalmSizeThreshold=900 +AttrThumbSizeThreshold=800 +AttrPalmPressureThreshold=190 + [Apple Touchpad OneButton] MatchUdevType=touchpad MatchBus=usb diff --git a/quirks/50-system-google.quirks b/quirks/50-system-google.quirks index cd31297..e04a31b 100644 --- a/quirks/50-system-google.quirks +++ b/quirks/50-system-google.quirks @@ -86,3 +86,11 @@ MatchUdevType=touchpad MatchName=Atmel maXTouch Touchpad MatchDMIModalias=dmi:*svn*GOOGLE*:pn*Samus* ModelChromebook=1 + +[Google Chromebook Eve] +MatchUdevType=touchpad +MatchName=ACPI0C50:00 18D1:5028 +MatchDMIModalias=dmi:*svnGoogle:pnEve* +ModelChromebook=1 +AttrPressureRange=6:4 +AttrThumbPressureThreshold=45 diff --git a/quirks/50-system-hp.quirks b/quirks/50-system-hp.quirks index 3f43cb3..0109991 100644 --- a/quirks/50-system-hp.quirks +++ b/quirks/50-system-hp.quirks @@ -24,6 +24,16 @@ MatchName=SYN1EDE:00 06CB:7442 MatchDMIModalias=dmi:*svnHewlett-Packard:pnHPStreamNotebookPC11* ModelHPStream11Touchpad=1 +# The HP stream x360's embedded-controller filters out events form its builtin +# keyboard when in tablet-mode itself; and it has a capacitive home-button +# (windows logo) underneath its display which also sends PS/2 key-events. +# Do not suspend the keyboard when in tablet-mode so that the home button +# keeps working when in tablet-mode. +[HP Stream x360 11] +MatchName=AT Translated Set 2 keyboard +MatchDMIModalias=dmi:*:svnHewlett-Packard:pnHPStreamx360ConvertiblePC11:* +ModelTabletModeNoSuspend=1 + [HP ZBook Studio G3] MatchName=AlpsPS/2 ALPS GlidePoint MatchDMIModalias=dmi:*svnHP:pnHPZBookStudioG3:* @@ -42,6 +52,14 @@ AttrPressureRange=55:40 AttrThumbPressureThreshold=90 AttrPalmPressureThreshold=100 +[HP Spectre x360 Convertable 15-ch0xx] +MatchUdevType=touchpad +MatchName=*SynPS/2 Synaptics TouchPad +MatchDMIModalias=dmi:*svnHP:pnHPSpectrex360Convertible15-ch0xx:* +AttrThumbPressureThreshold=90 +AttrPalmPressureThreshold=100 + + [HP Elite x2 1013 G3 Tablet Mode Switch] MatchName=*Intel Virtual Button* MatchDMIModalias=dmi:*svnHP:pnHPElitex21013G3:* diff --git a/quirks/50-system-lenovo.quirks b/quirks/50-system-lenovo.quirks index 2f0ca56..19a3b9e 100644 --- a/quirks/50-system-lenovo.quirks +++ b/quirks/50-system-lenovo.quirks @@ -134,8 +134,9 @@ AttrTrackpointMultiplier=1.25 # sends bogus ABS_MT_TOOL_TYPE events for MT_TOOL_PALM [Lenovo Carbon X1 6th gen] MatchName=Synaptics TM3288-011 -MatchDMIModalias=dmi:*svnLenovo:*pvrThinkPadX1Carbon6th:* +MatchDMIModalias=dmi:*svnLENOVO:*pvrThinkPadX1Carbon6th:* AttrEventCodeDisable=ABS_MT_TOOL_TYPE +ModelLenovoX1Gen6Touchpad=1 [Lenovo X41 Tablet] MatchName=AT Translated Set 2 keyboard @@ -169,6 +170,11 @@ MatchName=AT Translated Set 2 keyboard MatchDMIModalias=dmi:*svnLENOVO:*pvrThinkPadX200Tablet:* ModelTabletModeNoSuspend=1 +[Lenovo X201 Tablet] +MatchName=At Translated Set 2 keyboard +MatchDMIModalias=dmi:*svnLENOVO:*pvrThinkPadX201Tablet:* +ModelTabletModeNoSuspend=1 + # Lenovo MIIX 720 comes with a detachable keyboard. We must not disable # the keyboard because some keys are still accessible on the screen and # volume rocker. See @@ -177,3 +183,11 @@ ModelTabletModeNoSuspend=1 MatchName=AT Raw Set 2 keyboard MatchDMIModalias=dmi:*svnLENOVO:*pvrLenovoMIIX720-12IKB:* ModelTabletModeNoSuspend=1 + +# Lenovo ThinkPad X1 Tablet (1st Gen) also comes with a detachable keyboard. +# We must not disable the keyboard because some keys are still accessible on +# volume rocker. +[Lenovo ThinkPad X1 Tablet (1st Gen)] +MatchName=AT Raw Set 2 keyboard +MatchDMIModalias=dmi:*svnLENOVO:*pvrThinkPadX1Tablet:* +ModelTabletModeNoSuspend=1 diff --git a/quirks/50-system-sony.quirks b/quirks/50-system-sony.quirks new file mode 100644 index 0000000..22b08ff --- /dev/null +++ b/quirks/50-system-sony.quirks @@ -0,0 +1,8 @@ +# Do not edit this file, it will be overwritten on update + +[Sony Vaio VPCEG Series Touchpad Pressure Override] +MatchUdevType=touchpad +MatchName=*SynPS/2 Synaptics TouchPad +MatchDMIModalias=dmi:*svnSonyCorporation:pnVPCEG* +AttrPressureRange=45:40 + diff --git a/src/builddir.h b/src/builddir.h index 7fc377c..bc0c9e5 100644 --- a/src/builddir.h +++ b/src/builddir.h @@ -25,6 +25,9 @@ #pragma once +#include +#include "util-strings.h" + /** * Try to figure out the directory we're executing from and if it matches * the builddir, return that directory. Otherwise, return NULL. diff --git a/src/evdev-fallback.c b/src/evdev-fallback.c index 1a9113b..1e10fc3 100644 --- a/src/evdev-fallback.c +++ b/src/evdev-fallback.c @@ -29,6 +29,7 @@ #include #include "evdev-fallback.h" +#include "util-input-event.h" static void fallback_keyboard_notify_key(struct fallback_dispatch *dispatch, @@ -207,7 +208,6 @@ fallback_flush_wheels(struct fallback_dispatch *dispatch, { struct normalized_coords wheel_degrees = { 0.0, 0.0 }; struct discrete_coords discrete = { 0.0, 0.0 }; - enum libinput_pointer_axis_source source; if (!(device->seat_caps & EVDEV_DEVICE_POINTER)) return; @@ -232,15 +232,11 @@ fallback_flush_wheels(struct fallback_dispatch *dispatch, device->scroll.wheel_click_angle.y; discrete.y = -1 * dispatch->wheel.y; - source = device->scroll.is_tilt.vertical ? - LIBINPUT_POINTER_AXIS_SOURCE_WHEEL_TILT: - LIBINPUT_POINTER_AXIS_SOURCE_WHEEL; - evdev_notify_axis( device, time, bit(LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL), - source, + LIBINPUT_POINTER_AXIS_SOURCE_WHEEL, &wheel_degrees, &discrete); dispatch->wheel.y = 0; @@ -251,15 +247,11 @@ fallback_flush_wheels(struct fallback_dispatch *dispatch, device->scroll.wheel_click_angle.x; discrete.x = dispatch->wheel.x; - source = device->scroll.is_tilt.horizontal ? - LIBINPUT_POINTER_AXIS_SOURCE_WHEEL_TILT: - LIBINPUT_POINTER_AXIS_SOURCE_WHEEL; - evdev_notify_axis( device, time, bit(LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL), - source, + LIBINPUT_POINTER_AXIS_SOURCE_WHEEL, &wheel_degrees, &discrete); dispatch->wheel.x = 0; @@ -701,10 +693,10 @@ fallback_lid_keyboard_event(uint64_t time, if (dispatch->lid.reliability == RELIABILITY_WRITE_OPEN) { int fd = libevdev_get_fd(dispatch->device->evdev); int rc; - struct input_event ev[2] = { - {{ 0, 0 }, EV_SW, SW_LID, 0 }, - {{ 0, 0 }, EV_SYN, SYN_REPORT, 0 }, - }; + struct input_event ev[2]; + + ev[0] = input_event_init(0, EV_SW, SW_LID, 0); + ev[1] = input_event_init(0, EV_SYN, SYN_REPORT, 0); rc = write(fd, ev, sizeof(ev)); @@ -1225,7 +1217,7 @@ fallback_interface_update_rect(struct evdev_dispatch *evdev_dispatch, uint64_t time) { struct fallback_dispatch *dispatch = fallback_dispatch(evdev_dispatch); - struct device_coord_rect rect = {0}; + struct device_coord_rect rect; assert(phys_rect); @@ -1394,8 +1386,9 @@ fallback_pair_tablet_mode(struct evdev_device *keyboard, return; /* This filters out all internal keyboard-like devices (Video * Switch) */ - } else if ((keyboard->tags & EVDEV_TAG_INTERNAL_KEYBOARD) == 0) + } else if ((keyboard->tags & EVDEV_TAG_INTERNAL_KEYBOARD) == 0) { return; + } if (evdev_device_has_model_quirk(keyboard, QUIRK_MODEL_TABLET_MODE_NO_SUSPEND)) @@ -1499,7 +1492,8 @@ fallback_change_scroll_method(struct evdev_device *device) struct fallback_dispatch *dispatch = fallback_dispatch(device->dispatch); if (device->scroll.want_method == device->scroll.method && - device->scroll.want_button == device->scroll.button) + device->scroll.want_button == device->scroll.button && + device->scroll.want_lock_enabled == device->scroll.lock_enabled) return; if (fallback_any_button_down(dispatch, device)) @@ -1507,6 +1501,8 @@ fallback_change_scroll_method(struct evdev_device *device) device->scroll.method = device->scroll.want_method; device->scroll.button = device->scroll.want_button; + device->scroll.lock_enabled = device->scroll.want_lock_enabled; + evdev_set_button_scroll_lock_enabled(device, device->scroll.lock_enabled); } static int diff --git a/src/evdev-fallback.h b/src/evdev-fallback.h index 0f75827..d1223ba 100644 --- a/src/evdev-fallback.h +++ b/src/evdev-fallback.h @@ -194,7 +194,7 @@ get_key_type(uint16_t code) return KEY_TYPE_KEY; if (code >= BTN_DPAD_UP && code <= BTN_DPAD_RIGHT) return KEY_TYPE_BUTTON; - if (code >= KEY_ALS_TOGGLE && code <= KEY_ONSCREEN_KEYBOARD) + if (code >= KEY_ALS_TOGGLE && code < BTN_TRIGGER_HAPPY) return KEY_TYPE_KEY; if (code >= BTN_TRIGGER_HAPPY && code <= BTN_TRIGGER_HAPPY40) return KEY_TYPE_BUTTON; diff --git a/src/evdev-middle-button.c b/src/evdev-middle-button.c index 8c0685c..a9fb12a 100644 --- a/src/evdev-middle-button.c +++ b/src/evdev-middle-button.c @@ -31,9 +31,8 @@ /***************************************** * 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) + * doc/middle-button-emulation-state-machine.svg (generated with + * https://draw.io). * * Any changes in this file must be represented in the diagram. * @@ -553,7 +552,7 @@ evdev_middlebutton_handle_event(struct evdev_device *device, } evdev_log_debug(device, - "middlebuttonstate: %s → %s → %s, rc %d\n", + "middlebutton state: %s → %s → %s, rc %d\n", middlebutton_state_to_str(current), middlebutton_event_to_str(event), middlebutton_state_to_str(device->middlebutton.state), diff --git a/src/evdev-mt-touchpad-buttons.c b/src/evdev-mt-touchpad-buttons.c index 6e72193..2a6092b 100644 --- a/src/evdev-mt-touchpad-buttons.c +++ b/src/evdev-mt-touchpad-buttons.c @@ -23,13 +23,12 @@ #include "config.h" -#include #include #include #include -#include #include "linux/input.h" +#include "util-input-event.h" #include "evdev-mt-touchpad.h" #define DEFAULT_BUTTON_ENTER_TIMEOUT ms2us(100) @@ -37,10 +36,8 @@ /***************************************** * BEFORE YOU EDIT THIS FILE, look at the state diagram in - * doc/touchpad-softbutton-state-machine.svg, or online at - * https://drive.google.com/file/d/0B1NwWmji69nocUs1cVJTbkdwMFk/edit?usp=sharing - * (it's a http://draw.io diagram) - * + * doc/touchpad-softbutton-state-machine.svg (generated with + * https://draw.io). * Any changes in this file must be represented in the diagram. * * The state machine only affects the soft button area code. @@ -909,7 +906,7 @@ tp_init_middlebutton_emulation(struct tp_dispatch *tp, enable_by_default = true; want_config_option = false; } else if (evdev_device_has_model_quirk(device, - QUIRK_MODEL_ALPS_TOUCHPAD)) { + QUIRK_MODEL_ALPS_SERIAL_TOUCHPAD)) { enable_by_default = true; want_config_option = true; } else @@ -1145,14 +1142,12 @@ tp_notify_clickpadbutton(struct tp_dispatch *tp, if (tp->buttons.trackpoint) { if (is_topbutton) { struct evdev_dispatch *dispatch = tp->buttons.trackpoint->dispatch; - struct input_event event; - struct input_event syn_report = {{ 0, 0 }, EV_SYN, SYN_REPORT, 0 }; - - event.time = us2tv(time); - event.type = EV_KEY; - event.code = button; - event.value = (state == LIBINPUT_BUTTON_STATE_PRESSED) ? 1 : 0; - syn_report.time = event.time; + struct input_event event, syn_report; + int value; + + value = (state == LIBINPUT_BUTTON_STATE_PRESSED) ? 1 : 0; + event = input_event_init(time, EV_KEY, button, value); + syn_report = input_event_init(time, EV_SYN, SYN_REPORT, 0); dispatch->interface->process(dispatch, tp->buttons.trackpoint, &event, diff --git a/src/evdev-mt-touchpad-edge-scroll.c b/src/evdev-mt-touchpad-edge-scroll.c index 25e92a6..2cdec4c 100644 --- a/src/evdev-mt-touchpad-edge-scroll.c +++ b/src/evdev-mt-touchpad-edge-scroll.c @@ -23,12 +23,9 @@ #include "config.h" -#include #include #include #include -#include -#include "linux/input.h" #include "evdev-mt-touchpad.h" diff --git a/src/evdev-mt-touchpad-gestures.c b/src/evdev-mt-touchpad-gestures.c index 59e18bd..6c63183 100644 --- a/src/evdev-mt-touchpad-gestures.c +++ b/src/evdev-mt-touchpad-gestures.c @@ -25,7 +25,6 @@ #include #include -#include #include "evdev-mt-touchpad.h" @@ -391,28 +390,30 @@ tp_gesture_handle_state_none(struct tp_dispatch *tp, uint64_t time) first = touches[0]; second = touches[1]; - /* For 3+ finger gestures we cheat. A human hand's finger - * arrangement means that for a 3 or 4 finger swipe gesture, the - * fingers are roughly arranged in a horizontal line. - * They will all move in the same direction, so we can simply look - * at the left and right-most ones only. If we have fake touches, we - * just take the left/right-most real touch position, since the fake - * touch has the same location as one of those. + /* For 3+ finger gestures, we only really need to track two touches. + * The human hand's finger arrangement means that for a pinch, the + * bottom-most touch will always be the thumb, and the top-most touch + * will always be one of the fingers. * - * For a 3 or 4 finger pinch gesture, 2 or 3 fingers are roughly in - * a horizontal line, with the thumb below and left (right-handed - * users) or right (left-handed users). Again, the row of non-thumb - * fingers moves identically so we can look at the left and - * right-most only and then treat it like a two-finger - * gesture. + * For 3+ finger swipes, the fingers will likely (but not necessarily) + * be in a horizontal line. They all move together, regardless, so it + * doesn't really matter which two of those touches we track. + * + * Tracking top and bottom is a change from previous versions, where + * we tracked leftmost and rightmost. This change enables: + * + * - More accurate pinch detection if thumb is near the center + * - Better resting-thumb detection while two-finger scrolling + * - On capable hardware, allow 3- or 4-finger swipes with resting + * thumb or held-down clickpad */ if (ntouches > 2) { second = touches[0]; for (i = 1; i < ntouches && i < tp->num_slots; i++) { - if (touches[i]->point.x < first->point.x) + if (touches[i]->point.y < first->point.y) first = touches[i]; - else if (touches[i]->point.x > second->point.x) + else if (touches[i]->point.y >= second->point.y) second = touches[i]; } @@ -478,8 +479,8 @@ tp_gesture_handle_state_unknown(struct tp_dispatch *tp, uint64_t time) struct phys_coords first_moved, second_moved, distance_mm; double first_mm, second_mm; /* movement since gesture start in mm */ double thumb_mm, finger_mm; - double inner = 1.5; /* inner threshold in mm - count this touch */ - double outer = 4.0; /* outer threshold in mm - ignore other touch */ + double min_move = 1.5; /* min movement threshold in mm - count this touch */ + double max_move = 4.0; /* max movement threshold in mm - ignore other touch */ /* If we have more fingers than slots, we don't know where the * fingers are. Default to swipe */ @@ -488,8 +489,8 @@ tp_gesture_handle_state_unknown(struct tp_dispatch *tp, uint64_t time) return GESTURE_STATE_SWIPE; /* Need more margin for error when there are more fingers */ - outer += 2.0 * (tp->gesture.finger_count - 2); - inner += 0.5 * (tp->gesture.finger_count - 2); + max_move += 2.0 * (tp->gesture.finger_count - 2); + min_move += 0.5 * (tp->gesture.finger_count - 2); first_moved = tp_gesture_mm_moved(tp, first); first_mm = hypot(first_moved.x, first_moved.y); @@ -529,18 +530,18 @@ tp_gesture_handle_state_unknown(struct tp_dispatch *tp, uint64_t time) } } - /* If one touch exceeds the outer threshold while the other has not - * yet passed the inner threshold, there is either a resting thumb, + /* If one touch exceeds the max_move threshold while the other has not + * yet passed the min_move threshold, there is either a resting thumb, * or the user is doing "one-finger-scroll," where one touch stays in * place while the other moves. */ - if (first_mm >= outer || second_mm >= outer) { + if (first_mm >= max_move || second_mm >= max_move) { /* If thumb detection is enabled, and thumb is still while * finger moves, cancel gestures and mark lower as thumb. * This applies to all gestures (2, 3, 4+ fingers), but allows * more thumb motion on >2 finger gestures during detection. */ - if (tp->thumb.detect_thumbs && thumb_mm < inner) { + if (tp->thumb.detect_thumbs && thumb_mm < min_move) { tp_thumb_suppress(tp, thumb); return GESTURE_STATE_NONE; } @@ -549,7 +550,7 @@ tp_gesture_handle_state_unknown(struct tp_dispatch *tp, uint64_t time) * while thumb moves, assume this is "one-finger scrolling." * This applies only to 2-finger gestures. */ - if ((!tp->gesture.enabled || finger_mm < inner) && + if ((!tp->gesture.enabled || finger_mm < min_move) && tp->gesture.finger_count == 2) { tp_gesture_set_scroll_buildup(tp); return GESTURE_STATE_SCROLL; @@ -558,7 +559,7 @@ tp_gesture_handle_state_unknown(struct tp_dispatch *tp, uint64_t time) /* If more than 2 fingers are involved, and the thumb moves * while the fingers stay still, assume a pinch if eligible. */ - if (finger_mm < inner && + if (finger_mm < min_move && tp->gesture.finger_count > 2 && tp->gesture.enabled && tp->thumb.pinch_eligible) { @@ -567,15 +568,15 @@ tp_gesture_handle_state_unknown(struct tp_dispatch *tp, uint64_t time) } } - /* If either touch is still inside the inner threshold, we can't + /* If either touch is still below the min_move threshold, we can't * tell what kind of gesture this is. */ - if ((first_mm < inner) || (second_mm < inner)) + if ((first_mm < min_move) || (second_mm < min_move)) return GESTURE_STATE_UNKNOWN; - /* Both touches have exceeded the inner threshold, so we have a valid - * gesture. Update gesture initial time and get directions so we know - * if it's a pinch or swipe/scroll. + /* Both touches have exceeded the min_move threshold, so we have a + * valid gesture. Update gesture initial time and get directions so + * we know if it's a pinch or swipe/scroll. */ dir1 = tp_gesture_get_direction(tp, first); dir2 = tp_gesture_get_direction(tp, second); diff --git a/src/evdev-mt-touchpad-tap.c b/src/evdev-mt-touchpad-tap.c index a482849..f5cf731 100644 --- a/src/evdev-mt-touchpad-tap.c +++ b/src/evdev-mt-touchpad-tap.c @@ -24,11 +24,8 @@ #include "config.h" #include -#include #include #include -#include -#include #include "evdev-mt-touchpad.h" @@ -50,10 +47,8 @@ enum tap_event { /***************************************** * DO NOT EDIT THIS FILE! * - * Look at the state diagram in doc/touchpad-tap-state-machine.svg, or - * online at - * https://drive.google.com/file/d/0B1NwWmji69noYTdMcU1kTUZuUVE/edit?usp=sharing - * (it's a http://draw.io diagram) + * Look at the state diagram in doc/touchpad-tap-state-machine.svg + * (generated with https://draw.io) * * Any changes in this file must be represented in the diagram. */ @@ -76,9 +71,6 @@ tap_state_to_str(enum tp_tap_state state) 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_MULTITAP_PALM); CASE_RETURN_STRING(TAP_STATE_DEAD); } return NULL; @@ -160,6 +152,14 @@ tp_tap_clear_timer(struct tp_dispatch *tp) } static void +tp_tap_move_to_dead(struct tp_dispatch *tp, struct tp_touch *t) +{ + tp->tap.state = TAP_STATE_DEAD; + t->tap.state = TAP_TOUCH_STATE_DEAD; + tp_tap_clear_timer(tp); +} + +static void tp_tap_idle_handle_event(struct tp_dispatch *tp, struct tp_touch *t, enum tap_event event, uint64_t time) @@ -220,8 +220,10 @@ tp_tap_touch_handle_event(struct tp_dispatch *tp, tp->tap.state = TAP_STATE_IDLE; } break; - case TAP_EVENT_TIMEOUT: case TAP_EVENT_MOTION: + tp_tap_move_to_dead(tp, t); + break; + case TAP_EVENT_TIMEOUT: tp->tap.state = TAP_STATE_HOLD; tp_tap_clear_timer(tp); break; @@ -260,6 +262,8 @@ tp_tap_hold_handle_event(struct tp_dispatch *tp, tp->tap.state = TAP_STATE_IDLE; break; case TAP_EVENT_MOTION: + tp_tap_move_to_dead(tp, t); + break; case TAP_EVENT_TIMEOUT: break; case TAP_EVENT_BUTTON: @@ -335,8 +339,8 @@ tp_tap_touch2_handle_event(struct tp_dispatch *tp, tp_tap_set_timer(tp, time); break; case TAP_EVENT_MOTION: - tp_tap_clear_timer(tp); - /* fallthrough */ + tp_tap_move_to_dead(tp, t); + break; case TAP_EVENT_TIMEOUT: tp->tap.state = TAP_STATE_TOUCH_2_HOLD; break; @@ -370,6 +374,8 @@ tp_tap_touch2_hold_handle_event(struct tp_dispatch *tp, tp->tap.state = TAP_STATE_HOLD; break; case TAP_EVENT_MOTION: + tp_tap_move_to_dead(tp, t); + break; case TAP_EVENT_TIMEOUT: tp->tap.state = TAP_STATE_TOUCH_2_HOLD; break; @@ -410,6 +416,8 @@ tp_tap_touch2_release_handle_event(struct tp_dispatch *tp, tp->tap.state = TAP_STATE_IDLE; break; case TAP_EVENT_MOTION: + tp_tap_move_to_dead(tp, t); + break; case TAP_EVENT_TIMEOUT: tp->tap.state = TAP_STATE_HOLD; break; @@ -458,6 +466,8 @@ tp_tap_touch3_handle_event(struct tp_dispatch *tp, tp_tap_clear_timer(tp); break; case TAP_EVENT_MOTION: + tp_tap_move_to_dead(tp, t); + break; case TAP_EVENT_TIMEOUT: tp->tap.state = TAP_STATE_TOUCH_3_HOLD; tp_tap_clear_timer(tp); @@ -500,6 +510,8 @@ tp_tap_touch3_hold_handle_event(struct tp_dispatch *tp, tp->tap.state = TAP_STATE_TOUCH_2_HOLD; break; case TAP_EVENT_MOTION: + tp_tap_move_to_dead(tp, t); + break; case TAP_EVENT_TIMEOUT: break; case TAP_EVENT_BUTTON: @@ -525,11 +537,15 @@ 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_MULTITAP; + tp->tap.state = TAP_STATE_TAPPED; tp_tap_notify(tp, tp->tap.saved_release_time, 1, LIBINPUT_BUTTON_STATE_RELEASED); + tp_tap_notify(tp, + tp->tap.saved_press_time, + 1, + LIBINPUT_BUTTON_STATE_PRESSED); tp->tap.saved_release_time = time; tp_tap_set_timer(tp, time); break; @@ -698,124 +714,6 @@ 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) -{ - switch (event) { - case TAP_EVENT_RELEASE: - log_tap_bug(tp, t, event); - break; - case TAP_EVENT_TOUCH: - tp->tap.state = TAP_STATE_MULTITAP_DOWN; - tp_tap_notify(tp, - tp->tap.saved_press_time, - 1, - LIBINPUT_BUTTON_STATE_PRESSED); - tp->tap.saved_press_time = time; - tp_tap_set_timer(tp, time); - break; - case TAP_EVENT_MOTION: - log_tap_bug(tp, t, event); - break; - case TAP_EVENT_TIMEOUT: - tp->tap.state = TAP_STATE_IDLE; - tp_tap_notify(tp, - tp->tap.saved_press_time, - 1, - LIBINPUT_BUTTON_STATE_PRESSED); - tp_tap_notify(tp, - tp->tap.saved_release_time, - 1, - LIBINPUT_BUTTON_STATE_RELEASED); - break; - case TAP_EVENT_BUTTON: - tp->tap.state = TAP_STATE_IDLE; - tp_tap_clear_timer(tp); - break; - case TAP_EVENT_THUMB: - case TAP_EVENT_PALM: - break; - case TAP_EVENT_PALM_UP: - 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, - tp->tap.saved_release_time, - 1, - LIBINPUT_BUTTON_STATE_RELEASED); - tp->tap.saved_release_time = time; - tp_tap_set_timer(tp, time); - break; - case TAP_EVENT_TOUCH: - tp->tap.state = TAP_STATE_DRAGGING_2; - tp_tap_clear_timer(tp); - break; - case TAP_EVENT_MOTION: - case TAP_EVENT_TIMEOUT: - tp->tap.state = TAP_STATE_DRAGGING; - tp_tap_clear_timer(tp); - break; - case TAP_EVENT_BUTTON: - tp->tap.state = TAP_STATE_DEAD; - tp_tap_notify(tp, - tp->tap.saved_release_time, - 1, - LIBINPUT_BUTTON_STATE_RELEASED); - tp_tap_clear_timer(tp); - break; - case TAP_EVENT_THUMB: - break; - case TAP_EVENT_PALM: - tp->tap.state = TAP_STATE_MULTITAP_PALM; - break; - case TAP_EVENT_PALM_UP: - break; - } -} - -static void -tp_tap_multitap_palm_handle_event(struct tp_dispatch *tp, - struct tp_touch *t, - enum tap_event event, - uint64_t time) -{ - switch (event) { - case TAP_EVENT_RELEASE: - log_tap_bug(tp, t, event); - break; - case TAP_EVENT_TOUCH: - tp->tap.state = TAP_STATE_MULTITAP_DOWN; - break; - case TAP_EVENT_MOTION: - break; - case TAP_EVENT_TIMEOUT: - case TAP_EVENT_BUTTON: - tp->tap.state = TAP_STATE_IDLE; - tp_tap_clear_timer(tp); - tp_tap_notify(tp, - tp->tap.saved_release_time, - 1, - LIBINPUT_BUTTON_STATE_RELEASED); - break; - case TAP_EVENT_THUMB: - case TAP_EVENT_PALM: - case TAP_EVENT_PALM_UP: - break; - } -} - -static void tp_tap_dead_handle_event(struct tp_dispatch *tp, struct tp_touch *t, enum tap_event event, @@ -895,15 +793,6 @@ tp_tap_handle_event(struct tp_dispatch *tp, 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_MULTITAP_PALM: - tp_tap_multitap_palm_handle_event(tp, t, event, time); - break; case TAP_STATE_DEAD: tp_tap_dead_handle_event(tp, t, event, time); break; @@ -914,8 +803,9 @@ tp_tap_handle_event(struct tp_dispatch *tp, if (current != tp->tap.state) evdev_log_debug(tp->device, - "tap: touch %d state %s → %s → %s\n", + "tap: touch %d (%s), tap state %s → %s → %s\n", t ? (int)t->index : -1, + t ? touch_state_to_str(t->state) : "", tap_state_to_str(current), tap_event_to_str(event), tap_state_to_str(tp->tap.state)); @@ -1004,10 +894,10 @@ tp_tap_handle_state(struct tp_dispatch *tp, uint64_t time) if (t->palm.state != PALM_NONE) { assert(!t->tap.is_palm); - tp_tap_handle_event(tp, t, TAP_EVENT_PALM, time); t->tap.is_palm = true; t->tap.state = TAP_TOUCH_STATE_DEAD; if (t->state != TOUCH_BEGIN) { + tp_tap_handle_event(tp, t, TAP_EVENT_PALM, time); assert(tp->tap.nfingers_down > 0); tp->tap.nfingers_down--; } @@ -1070,7 +960,6 @@ tp_tap_handle_state(struct tp_dispatch *tp, uint64_t time) 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; diff --git a/src/evdev-mt-touchpad-thumb.c b/src/evdev-mt-touchpad-thumb.c index 19531e0..2beaa54 100644 --- a/src/evdev-mt-touchpad-thumb.c +++ b/src/evdev-mt-touchpad-thumb.c @@ -28,6 +28,7 @@ /* distance between fingers to assume it is not a scroll */ #define SCROLL_MM_X 35 #define SCROLL_MM_Y 25 +#define THUMB_TIMEOUT ms2us(100) static inline const char* thumb_state_to_str(enum tp_thumb_state state) @@ -271,13 +272,15 @@ tp_thumb_update_multifinger(struct tp_dispatch *tp) struct tp_touch *t; struct tp_touch *first = NULL, *second = NULL, - *newest = NULL; + *newest = NULL, + *oldest = NULL; struct device_coords distance; struct phys_coords mm; + unsigned int speed_exceeded_count = 0; /* Get the first and second bottom-most touches, the max speed exceeded - * count overall, and the newest touch (or one of them, if more). + * count overall, and the newest and oldest touches. */ tp_for_each_touch(tp, t) { if (t->state == TOUCH_NONE || @@ -290,6 +293,10 @@ tp_thumb_update_multifinger(struct tp_dispatch *tp) speed_exceeded_count = max(speed_exceeded_count, t->speed.exceeded_count); + if (!oldest || t->initial_time < oldest->initial_time) { + oldest = t; + } + if (!first) { first = t; continue; @@ -309,20 +316,18 @@ tp_thumb_update_multifinger(struct tp_dispatch *tp) if (!first || !second) return; - assert(first); - assert(second); - distance.x = abs(first->point.x - second->point.x); distance.y = abs(first->point.y - second->point.y); mm = evdev_device_unit_delta_to_mm(tp->device, &distance); /* Speed-based thumb detection: if an existing finger is moving, and * a new touch arrives, mark it as a thumb if it doesn't qualify as a - * 2-finger scroll. + * 2-finger scroll. Also account for a thumb dropping onto the touchpad + * while scrolling or swiping. */ if (newest && tp->thumb.state == THUMB_STATE_FINGER && - tp->nfingers_down == 2 && + tp->nfingers_down >= 2 && speed_exceeded_count > 5 && (tp->scroll.method != LIBINPUT_CONFIG_SCROLL_2FG || (mm.x > SCROLL_MM_X || mm.y > SCROLL_MM_Y))) { @@ -333,20 +338,43 @@ tp_thumb_update_multifinger(struct tp_dispatch *tp) return; } - /* Position-based thumb detection: When a new touch arrives, check the - * two lowest touches. If they qualify for 2-finger scrolling, clear - * thumb status. + /* Contextual thumb detection: When a new touch arrives, check the + * timing and position of the two lowest touches. * - * If they were in distinct diagonal position, then mark the lower - * touch (based on pinch_eligible) as either PINCH or SUPPRESSED. If - * we're too close together for a thumb, lift that. + * If both touches are very close, regardless of timing, and no matter + * their absolute position on the touchpad, count them both as live + * to support responsive two-finger scrolling. + */ + + if (mm.x < SCROLL_MM_X && mm.y < SCROLL_MM_Y) { + tp_thumb_lift(tp); + return; + } + + /* If all the touches arrived within a very short time, and all of them + * are above the lower_thumb_line, assume the touches are all live to + * enable double, triple, and quadruple taps, clicks, and gestures. (If + * there is an actual resting thumb, it will be detected later based on + * the behavior of the other touches.) */ - if (mm.y > SCROLL_MM_Y && mm.x > SCROLL_MM_X) { + + if ((newest->initial_time - oldest->initial_time) < THUMB_TIMEOUT && + first->point.y < tp->thumb.lower_thumb_line) { + tp_thumb_lift(tp); + return; + } + + /* If we're past the THUMB_TIMEOUT, and the touches are relatively far + * apart, then the new touch is unlikely to be a tap or clickfinger. + * Proceed with pre-1.14.901 thumb detection. + */ + + if (mm.y > SCROLL_MM_Y) { if (tp->thumb.pinch_eligible) tp_thumb_pinch(tp, first); else tp_thumb_suppress(tp, first); - } else if (mm.x < SCROLL_MM_X && mm.y < SCROLL_MM_Y) { + } else { tp_thumb_lift(tp); } } diff --git a/src/evdev-mt-touchpad.c b/src/evdev-mt-touchpad.c index 4ffc4a3..4064a8e 100644 --- a/src/evdev-mt-touchpad.c +++ b/src/evdev-mt-touchpad.c @@ -350,6 +350,7 @@ tp_begin_touch(struct tp_dispatch *tp, struct tp_touch *t, uint64_t time) t->dirty = true; t->state = TOUCH_BEGIN; t->time = time; + t->initial_time = time; t->was_down = true; tp->nfingers_down++; t->palm.time = time; @@ -525,10 +526,14 @@ tp_process_absolute(struct tp_dispatch *tp, tp->slot = e->value; break; case ABS_MT_TRACKING_ID: - if (e->value != -1) + if (e->value != -1) { + tp->nactive_slots += 1; tp_new_touch(tp, t, time); - else + } else { + assert(tp->nactive_slots >= 1); + tp->nactive_slots -= 1; tp_end_sequence(tp, t, time); + } break; case ABS_MT_PRESSURE: t->pressure = e->value; @@ -605,11 +610,14 @@ tp_restore_synaptics_touches(struct tp_dispatch *tp, (tp->nfingers_down == tp->num_slots && nfake_touches == tp->num_slots)) return; - /* Synaptics devices may end touch 2 on BTN_TOOL_TRIPLETAP - * and start it again on the next frame with different coordinates - * (#91352). We search the touches we have, if there is one that has - * just ended despite us being on tripletap, we move it back to - * update. + /* Synaptics devices may end touch 2 on transition to/fro + * BTN_TOOL_TRIPLETAP and start it again on the next frame with + * different coordinates (bz#91352, gitlab#434). We search the + * touches we have, if there is one that has just ended despite us + * being on tripletap, we move it back to update. + * + * Note: we only handle the transition from 2 to 3 touches, not the + * other way round (see gitlab#434) */ for (i = 0; i < tp->num_slots; i++) { struct tp_touch *t = tp_get_touch(tp, i); @@ -638,6 +646,47 @@ tp_process_fake_touches(struct tp_dispatch *tp, EVDEV_MODEL_SYNAPTICS_SERIAL_TOUCHPAD) tp_restore_synaptics_touches(tp, time); + /* ALPS serial touchpads always set 3 slots in the kernel, even + * where they support less than that. So we get BTN_TOOL_TRIPLETAP + * but never slot 2 because our slot count is wrong. + * This also means that the third touch falls through the cracks and + * is ignored. + * + * See https://gitlab.freedesktop.org/libinput/libinput/issues/408 + * + * All touchpad devices have at least one slot so we only do this + * for 2 touches or higher. + * + * There's an bug in libevdev < 1.9.0 affecting slots after a + * SYN_DROPPED. Where a user release one or more touches during + * SYN_DROPPED and places new ones on the touchpad, we may end up + * with fake touches but no active slots. + * So let's check for nactive_slots > 0 to make sure we don't lose + * all fingers. That's a workaround only, this must be fixed in + * libevdev. + * + * For a long explanation of what happens, see + * https://gitlab.freedesktop.org/libevdev/libevdev/merge_requests/19 + */ + if (tp->device->model_flags & EVDEV_MODEL_ALPS_SERIAL_TOUCHPAD && + nfake_touches > 1 && tp->has_mt && + tp->nactive_slots > 0 && + nfake_touches > tp->nactive_slots && + tp->nactive_slots < tp->num_slots) { + evdev_log_bug_kernel(tp->device, + "Wrong slot count (%d), reducing to %d\n", + tp->num_slots, + tp->nactive_slots); + /* This should be safe since we fill the slots from the + * first one so hiding the excessive slots shouldn't matter. + * There are sequences where we could accidentally lose an + * actual touch point but that requires specially crafted + * sequences and let's deal with that when it happens. + */ + tp->num_slots = tp->nactive_slots; + } + + start = tp->has_mt ? tp->num_slots : 0; for (i = start; i < tp->ntouches; i++) { t = tp_get_touch(tp, i); @@ -699,6 +748,10 @@ tp_process_key(struct tp_dispatch *tp, const struct input_event *e, uint64_t time) { + /* ignore kernel key repeat */ + if (e->value == 2) + return; + switch (e->code) { case BTN_LEFT: case BTN_MIDDLE: @@ -1148,8 +1201,9 @@ out: break; } evdev_log_debug(tp->device, - "palm: touch %d, palm detected (%s)\n", + "palm: touch %d (%s), palm detected (%s)\n", t->index, + touch_state_to_str(t->state), palm_state); } @@ -1445,6 +1499,13 @@ tp_detect_jumps(const struct tp_dispatch *tp, * were measured from */ unsigned int reference_interval = ms2us(12); + /* On some touchpads the firmware does funky stuff and we cannot + * have our own jump detection, e.g. Lenovo Carbon X1 Gen 6 (see + * issue #506) + */ + if (tp->jump.detection_disabled) + return false; + /* We haven't seen pointer jumps on Wacom tablets yet, so exclude * those. */ @@ -1481,6 +1542,19 @@ tp_detect_jumps(const struct tp_dispatch *tp, abs_distance = hypot(mm.x, mm.y) * reference_interval/tdelta; rel_distance = abs_distance - t->jumps.last_delta_mm; + /* Special case for the ALPS devices in the Lenovo ThinkPad E465, + * E550. These devices send occasional 4095/0 events on two fingers + * before snapping back to the correct position. + * https://gitlab.freedesktop.org/libinput/libinput/-/issues/492 + * The specific values are hardcoded here, if this ever happens on + * any other device we can make it absmax/absmin instead. + */ + if (tp->device->model_flags & EVDEV_MODEL_ALPS_SERIAL_TOUCHPAD && + t->point.x == 4095 && t->point.y == 0) { + t->point = last->point; + return true; + } + /* Cursor jump if: * - current single-event delta is >20mm, or * - we increased the delta by over 7mm within a 12ms frame. @@ -1670,10 +1744,11 @@ tp_process_state(struct tp_dispatch *tp, uint64_t time) if (tp_detect_jumps(tp, t, time)) { if (!tp->semi_mt) - evdev_log_bug_kernel(tp->device, - "Touch jump detected and discarded.\n" - "See %stouchpad-jumping-cursors.html for details\n", - HTTP_DOC_LINK); + evdev_log_bug_kernel_ratelimit(tp->device, + &tp->jump.warning, + "Touch jump detected and discarded.\n" + "See %stouchpad-jumping-cursors.html for details\n", + HTTP_DOC_LINK); tp_motion_history_reset(t); } @@ -2021,6 +2096,7 @@ tp_sync_touch(struct tp_dispatch *tp, int slot) { struct libevdev *evdev = device->evdev; + int tracking_id; if (!libevdev_fetch_slot_value(evdev, slot, @@ -2049,6 +2125,13 @@ tp_sync_touch(struct tp_dispatch *tp, slot, ABS_MT_TOUCH_MINOR, &t->minor); + + if (libevdev_fetch_slot_value(evdev, + slot, + ABS_MT_TRACKING_ID, + &tracking_id) && + tracking_id != -1) + tp->nactive_slots++; } static void @@ -2624,10 +2707,15 @@ evdev_tag_touchpad(struct evdev_device *device, } } - /* simple approach: touchpads on USB or Bluetooth are considered - * external, anything else is internal. Exception is Apple - - * internal touchpads are connected over USB and it doesn't have - * external USB touchpads anyway. + /* The hwdb is the authority on integration, these heuristics are + * the fallback only (they precede the hwdb too). + * + * Simple approach: USB is unknown, with the exception + * of Apple where internal touchpads are connected over USB and it + * doesn't have external USB touchpads anyway. + * + * Bluetooth touchpads are considered external, anything else is + * internal. */ bustype = libevdev_get_id_bustype(device->evdev); vendor = libevdev_get_id_vendor(device->evdev); @@ -2854,33 +2942,12 @@ tp_init_slots(struct tp_dispatch *tp, return true; } -static uint32_t -tp_accel_config_get_profiles(struct libinput_device *libinput_device) -{ - return LIBINPUT_CONFIG_ACCEL_PROFILE_NONE; -} - static enum libinput_config_status tp_accel_config_set_profile(struct libinput_device *libinput_device, - enum libinput_config_accel_profile profile) -{ - return LIBINPUT_CONFIG_STATUS_UNSUPPORTED; -} - -static enum libinput_config_accel_profile -tp_accel_config_get_profile(struct libinput_device *libinput_device) -{ - return LIBINPUT_CONFIG_ACCEL_PROFILE_NONE; -} - -static enum libinput_config_accel_profile -tp_accel_config_get_default_profile(struct libinput_device *libinput_device) -{ - return LIBINPUT_CONFIG_ACCEL_PROFILE_NONE; -} + enum libinput_config_accel_profile profile); static bool -tp_init_accel(struct tp_dispatch *tp) +tp_init_accel(struct tp_dispatch *tp, enum libinput_config_accel_profile which) { struct evdev_device *device = tp->device; int res_x, res_y; @@ -2902,8 +2969,10 @@ tp_init_accel(struct tp_dispatch *tp) tp->accel.y_scale_coeff = (DEFAULT_MOUSE_DPI/25.4) / res_y; tp->accel.xy_scale_coeff = 1.0 * res_x/res_y; - if (evdev_device_has_model_quirk(device, QUIRK_MODEL_LENOVO_X230) || - tp->device->model_flags & EVDEV_MODEL_LENOVO_X220_TOUCHPAD_FW81) + if (which == LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT) + filter = create_pointer_accelerator_filter_touchpad_flat(dpi); + else if (evdev_device_has_model_quirk(device, QUIRK_MODEL_LENOVO_X230) || + tp->device->model_flags & EVDEV_MODEL_LENOVO_X220_TOUCHPAD_FW81) filter = create_pointer_accelerator_filter_lenovo_x230(dpi, use_v_avg); else if (libevdev_get_id_bustype(device->evdev) == BUS_BLUETOOTH) filter = create_pointer_accelerator_filter_touchpad(dpi, @@ -2918,16 +2987,49 @@ tp_init_accel(struct tp_dispatch *tp) evdev_device_init_pointer_acceleration(tp->device, filter); - /* we override the profile hooks for accel configuration with hooks - * that don't allow selection of profiles */ - device->pointer.config.get_profiles = tp_accel_config_get_profiles; device->pointer.config.set_profile = tp_accel_config_set_profile; - device->pointer.config.get_profile = tp_accel_config_get_profile; - device->pointer.config.get_default_profile = tp_accel_config_get_default_profile; return true; } +static enum libinput_config_status +tp_accel_config_set_speed(struct libinput_device *device, double speed) +{ + struct evdev_device *dev = evdev_device(device); + + if (!filter_set_speed(dev->pointer.filter, speed)) + return LIBINPUT_CONFIG_STATUS_INVALID; + + return LIBINPUT_CONFIG_STATUS_SUCCESS; +} + +static enum libinput_config_status +tp_accel_config_set_profile(struct libinput_device *libinput_device, + enum libinput_config_accel_profile profile) +{ + struct evdev_device *device = evdev_device(libinput_device); + struct tp_dispatch *tp = tp_dispatch(device->dispatch); + struct motion_filter *filter; + double speed; + + filter = device->pointer.filter; + if (filter_get_type(filter) == profile) + return LIBINPUT_CONFIG_STATUS_SUCCESS; + + speed = filter_get_speed(filter); + device->pointer.filter = NULL; + + if (tp_init_accel(tp, profile)) { + tp_accel_config_set_speed(libinput_device, speed); + filter_destroy(filter); + } else { + device->pointer.filter = filter; + return LIBINPUT_CONFIG_STATUS_UNSUPPORTED; + } + + return LIBINPUT_CONFIG_STATUS_SUCCESS; +} + static uint32_t tp_scroll_get_methods(struct tp_dispatch *tp) { @@ -3525,13 +3627,16 @@ tp_init(struct tp_dispatch *tp, if (!use_touch_size) tp_init_pressure(tp, device); + /* 5 warnings per 2 hours should be enough */ + ratelimit_init(&tp->jump.warning, s2us(2 * 60 * 60), 5); + /* Set the dpi to that of the x axis, because that's what we normalize to when needed*/ device->dpi = device->abs.absinfo_x->resolution * 25.4; tp_init_hysteresis(tp); - if (!tp_init_accel(tp)) + if (!tp_init_accel(tp, LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE)) return false; tp_init_tap(tp); @@ -3543,6 +3648,14 @@ tp_init(struct tp_dispatch *tp, tp_init_gesture(tp); tp_init_thumb(tp); + /* Lenovo X1 Gen6 buffers the events in a weird way, making jump + * detection impossible. See + * https://gitlab.freedesktop.org/libinput/libinput/-/issues/506 + */ + if (evdev_device_has_model_quirk(device, + QUIRK_MODEL_LENOVO_X1GEN6_TOUCHPAD)) + tp->jump.detection_disabled = true; + device->seat_caps |= EVDEV_DEVICE_POINTER; if (tp->gesture.enabled) device->seat_caps |= EVDEV_DEVICE_GESTURE; diff --git a/src/evdev-mt-touchpad.h b/src/evdev-mt-touchpad.h index 5df284f..da56926 100644 --- a/src/evdev-mt-touchpad.h +++ b/src/evdev-mt-touchpad.h @@ -27,7 +27,6 @@ #include #include "evdev.h" -#include "filter.h" #include "timer.h" #define TOUCHPAD_HISTORY_LENGTH 4 @@ -54,6 +53,20 @@ enum touch_state { TOUCH_END = 5, }; +static inline const char * +touch_state_to_str(enum touch_state state) +{ + switch(state) { + CASE_RETURN_STRING(TOUCH_NONE); + CASE_RETURN_STRING(TOUCH_HOVERING); + CASE_RETURN_STRING(TOUCH_BEGIN); + CASE_RETURN_STRING(TOUCH_UPDATE); + CASE_RETURN_STRING(TOUCH_MAYBE_END); + CASE_RETURN_STRING(TOUCH_END); + } + return NULL; +} + enum touch_palm_state { PALM_NONE = 0, PALM_EDGE, @@ -104,9 +117,6 @@ enum tp_tap_state { TAP_STATE_DRAGGING, TAP_STATE_DRAGGING_WAIT, TAP_STATE_DRAGGING_2, - TAP_STATE_MULTITAP, - TAP_STATE_MULTITAP_DOWN, - TAP_STATE_MULTITAP_PALM, TAP_STATE_DEAD, /**< finger count exceeded */ }; @@ -162,6 +172,7 @@ struct tp_touch { bool dirty; struct device_coords point; uint64_t time; + uint64_t initial_time; int pressure; bool is_tool_palm; /* MT_TOOL_PALM */ int major, minor; @@ -272,6 +283,7 @@ struct tp_dispatch { struct libinput_timer arbitration_timer; } arbitration; + unsigned int nactive_slots; /* number of active slots */ unsigned int num_slots; /* number of slots */ unsigned int ntouches; /* no slots inc. fakes */ struct tp_touch *touches; /* len == ntouches */ @@ -282,6 +294,11 @@ struct tp_dispatch { */ unsigned int fake_touches; + struct { + bool detection_disabled; + struct ratelimit warning; + } jump; + /* if pressure goes above high -> touch down, if pressure then goes below low -> touch up */ struct { diff --git a/src/evdev-tablet-pad-leds.c b/src/evdev-tablet-pad-leds.c index b741d0c..ff21878 100644 --- a/src/evdev-tablet-pad-leds.c +++ b/src/evdev-tablet-pad-leds.c @@ -23,9 +23,7 @@ #include "config.h" -#include #include -#include #include #include "evdev-tablet-pad.h" diff --git a/src/evdev-tablet-pad.c b/src/evdev-tablet-pad.c index cb02726..c55aafc 100644 --- a/src/evdev-tablet-pad.c +++ b/src/evdev-tablet-pad.c @@ -332,6 +332,10 @@ pad_process_key(struct pad_dispatch *pad, uint32_t button = e->code; uint32_t is_press = e->value != 0; + /* ignore kernel key repeat */ + if (e->value == 2) + return; + pad_button_set_down(pad, button, is_press); } @@ -369,7 +373,7 @@ pad_notify_button_mask(struct pad_dispatch *pad, code = i * 8; while (buttons_slice) { int enabled; - char map; + key_or_button_map_t map; code++; enabled = (buttons_slice & 1); @@ -379,10 +383,28 @@ pad_notify_button_mask(struct pad_dispatch *pad, continue; map = pad->button_map[code - 1]; - if (map != -1) { - group = pad_button_get_mode_group(pad, map); - pad_button_update_mode(group, map, state); - tablet_pad_notify_button(base, time, map, state, group); + if (map_is_unmapped(map)) + continue; + + if (map_is_button(map)) { + int32_t button = map_value(map); + + group = pad_button_get_mode_group(pad, button); + pad_button_update_mode(group, button, state); + tablet_pad_notify_button(base, + time, + button, + state, + group); + } else if (map_is_key(map)) { + uint32_t key = map_value(map); + + tablet_pad_notify_key(base, + time, + key, + (enum libinput_key_state)state); + } else { + abort(); } } } @@ -554,7 +576,7 @@ pad_init_buttons_from_libwacom(struct pad_dispatch *pad, if (code == 0) continue; - pad->button_map[code] = map++; + map_set_button_map(pad->button_map[code], map++); } pad->nbuttons = map; @@ -579,39 +601,60 @@ pad_init_buttons_from_kernel(struct pad_dispatch *pad, /* we match wacom_report_numbered_buttons() from the kernel */ for (code = BTN_0; code < BTN_0 + 10; code++) { if (libevdev_has_event_code(device->evdev, EV_KEY, code)) - pad->button_map[code] = map++; + map_set_button_map(pad->button_map[code], map++); } for (code = BTN_BASE; code < BTN_BASE + 2; code++) { if (libevdev_has_event_code(device->evdev, EV_KEY, code)) - pad->button_map[code] = map++; + map_set_button_map(pad->button_map[code], map++); } for (code = BTN_A; code < BTN_A + 6; code++) { if (libevdev_has_event_code(device->evdev, EV_KEY, code)) - pad->button_map[code] = map++; + map_set_button_map(pad->button_map[code], map++); } for (code = BTN_LEFT; code < BTN_LEFT + 7; code++) { if (libevdev_has_event_code(device->evdev, EV_KEY, code)) - pad->button_map[code] = map++; + map_set_button_map(pad->button_map[code], map++); } pad->nbuttons = map; } static void +pad_init_keys(struct pad_dispatch *pad, struct evdev_device *device) +{ + unsigned int codes[] = { + KEY_BUTTONCONFIG, + KEY_ONSCREEN_KEYBOARD, + KEY_CONTROLPANEL, + }; + unsigned int *code; + + /* Wacom's keys are the only ones we know anything about */ + if (libevdev_get_id_vendor(device->evdev) != VENDOR_ID_WACOM) + return; + + ARRAY_FOR_EACH(codes, code) { + if (libevdev_has_event_code(device->evdev, EV_KEY, *code)) + map_set_key_map(pad->button_map[*code], *code); + } +} + +static void pad_init_buttons(struct pad_dispatch *pad, struct evdev_device *device) { size_t i; for (i = 0; i < ARRAY_LENGTH(pad->button_map); i++) - pad->button_map[i] = -1; + map_init(pad->button_map[i]); if (!pad_init_buttons_from_libwacom(pad, device)) pad_init_buttons_from_kernel(pad, device); + pad_init_keys(pad, device); } static void @@ -708,6 +751,15 @@ evdev_tablet_pad_create(struct evdev_device *device) } int +evdev_device_tablet_pad_has_key(struct evdev_device *device, uint32_t code) +{ + if (!(device->seat_caps & EVDEV_DEVICE_TABLET_PAD)) + return -1; + + return libevdev_has_event_code(device->evdev, EV_KEY, code); +} + +int evdev_device_tablet_pad_get_num_buttons(struct evdev_device *device) { struct pad_dispatch *pad = (struct pad_dispatch*)device->dispatch; diff --git a/src/evdev-tablet-pad.h b/src/evdev-tablet-pad.h index 6208caf..feb9527 100644 --- a/src/evdev-tablet-pad.h +++ b/src/evdev-tablet-pad.h @@ -47,6 +47,18 @@ struct button_state { unsigned char bits[NCHARS(KEY_CNT)]; }; +typedef struct { + uint32_t value; +} key_or_button_map_t; + +#define map_init(x_) ((x_).value = (uint32_t)-1) +#define map_is_unmapped(x_) ((x_).value == (uint32_t)-1) +#define map_is_button(x_) (((x_).value & 0xFF000000) == 0) +#define map_is_key(x_) (((x_).value & 0xFF000000) != 0) +#define map_set_button_map(field_, value_) ((field_).value = value_) +#define map_set_key_map(field_, value_) ((field_).value = value_ | 0xFF000000) +#define map_value(x_) ((x_).value & 0x00FFFFFF) + struct pad_dispatch { struct evdev_dispatch base; struct evdev_device *device; @@ -56,7 +68,7 @@ struct pad_dispatch { struct button_state button_state; struct button_state prev_button_state; - char button_map[KEY_CNT]; + key_or_button_map_t button_map[KEY_CNT]; unsigned int nbuttons; bool have_abs_misc_terminator; diff --git a/src/evdev-tablet.c b/src/evdev-tablet.c index 6358dc4..9c66782 100644 --- a/src/evdev-tablet.c +++ b/src/evdev-tablet.c @@ -22,7 +22,6 @@ * DEALINGS IN THE SOFTWARE. */ #include "config.h" -#include "libinput-version.h" #include "evdev-tablet.h" #include @@ -813,6 +812,10 @@ tablet_process_key(struct tablet_dispatch *tablet, { enum libinput_tablet_tool_type type; + /* ignore kernel key repeat */ + if (e->value == 2) + return; + switch (e->code) { case BTN_TOOL_FINGER: evdev_log_bug_libinput(device, @@ -1067,6 +1070,49 @@ axis_range_percentage(const struct input_absinfo *a, double percent) return (a->maximum - a->minimum) * percent/100.0 + a->minimum; } +static inline void +tool_set_pressure_thresholds(struct tablet_dispatch *tablet, + struct libinput_tablet_tool *tool) +{ + struct evdev_device *device = tablet->device; + const struct input_absinfo *pressure; + struct quirks_context *quirks = NULL; + struct quirks *q = NULL; + struct quirk_range r; + int lo = 0, hi = 1; + + tool->pressure_offset = 0; + tool->has_pressure_offset = false; + + pressure = libevdev_get_abs_info(device->evdev, ABS_PRESSURE); + if (!pressure) + goto out; + + quirks = evdev_libinput_context(device)->quirks; + q = quirks_fetch_for_device(quirks, device->udev_device); + + tool->pressure_offset = pressure->minimum; + + /* 5 and 1% of the pressure range */ + hi = axis_range_percentage(pressure, 5); + lo = axis_range_percentage(pressure, 1); + + if (q && quirks_get_range(q, QUIRK_ATTR_PRESSURE_RANGE, &r)) { + if (r.lower >= r.upper) { + evdev_log_info(device, + "Invalid pressure range, using defaults\n"); + } else { + hi = r.upper; + lo = r.lower; + } + } +out: + tool->pressure_threshold.upper = hi; + tool->pressure_threshold.lower = lo; + + quirks_unref(q); +} + static struct libinput_tablet_tool * tablet_get_tool(struct tablet_dispatch *tablet, enum libinput_tablet_tool_type type, @@ -1117,8 +1163,6 @@ tablet_get_tool(struct tablet_dispatch *tablet, /* If we didn't already have the new_tool in our list of tools, * add it */ if (!tool) { - const struct input_absinfo *pressure; - tool = zalloc(sizeof *tool); *tool = (struct libinput_tablet_tool) { @@ -1128,23 +1172,7 @@ tablet_get_tool(struct tablet_dispatch *tablet, .refcount = 1, }; - tool->pressure_offset = 0; - tool->has_pressure_offset = false; - tool->pressure_threshold.lower = 0; - tool->pressure_threshold.upper = 1; - - pressure = libevdev_get_abs_info(tablet->device->evdev, - ABS_PRESSURE); - if (pressure) { - tool->pressure_offset = pressure->minimum; - - /* 5 and 1% of the pressure range */ - tool->pressure_threshold.upper = - axis_range_percentage(pressure, 5); - tool->pressure_threshold.lower = - axis_range_percentage(pressure, 1); - } - + tool_set_pressure_thresholds(tablet, tool); tool_set_bits(tablet, tool); list_insert(tool_list, &tool->link); @@ -1732,7 +1760,7 @@ tablet_proximity_out_quirk_set_timer(struct tablet_dispatch *tablet, time + FORCED_PROXOUT_TIMEOUT); } -static void +static bool tablet_update_tool_state(struct tablet_dispatch *tablet, struct evdev_device *device, uint64_t time) @@ -1740,7 +1768,18 @@ tablet_update_tool_state(struct tablet_dispatch *tablet, enum libinput_tablet_tool_type type; uint32_t changed; int state; + uint32_t doubled_up_new_tool_bit = 0; + /* we were already out of proximity but now got a tool update but + * our tool state is zero - i.e. we got a valid prox out from the + * device. + */ + if (tablet->quirks.proximity_out_forced && + tablet_has_status(tablet, TABLET_TOOL_UPDATED) && + !tablet->tool_state) { + tablet->quirks.need_to_force_prox_out = false; + tablet->quirks.proximity_out_forced = false; + } /* We need to emulate a BTN_TOOL_PEN if we get an axis event (i.e. * stylus is def. in proximity) and: * - we forced a proximity out before, or @@ -1755,8 +1794,8 @@ tablet_update_tool_state(struct tablet_dispatch *tablet, */ if (tablet_has_status(tablet, TABLET_AXES_UPDATED)) { if (tablet->quirks.proximity_out_forced) { - if (!tablet_has_status(tablet, TABLET_TOOL_UPDATED) || - tablet->tool_state) + if (!tablet_has_status(tablet, TABLET_TOOL_UPDATED) && + !tablet->tool_state) tablet->tool_state = bit(LIBINPUT_TABLET_TOOL_TYPE_PEN); tablet->quirks.proximity_out_forced = false; } else if (tablet->tool_state == 0 && @@ -1767,16 +1806,23 @@ tablet_update_tool_state(struct tablet_dispatch *tablet, } if (tablet->tool_state == tablet->prev_tool_state) - return; + return false; /* Kernel tools are supposed to be mutually exclusive, if we have - * two set discard the most recent one. */ + * two, we force a proximity out for the older tool and handle the + * new tool as separate proximity in event. + */ if (tablet->tool_state & (tablet->tool_state - 1)) { - evdev_log_bug_kernel(device, - "Multiple tools active simultaneously (%#x)\n", - tablet->tool_state); - tablet->tool_state = tablet->prev_tool_state; - goto out; + /* tool_state has 2 bits set. We set the current tool state + * to zero, thus setting everything up for a prox out on the + * tool. Once that is set up, we change the tool state to be + * the new one we just got so when we re-process this + * function we now get the new tool as prox in. + * Importantly, we basically rely on nothing else happening + * in the meantime. + */ + doubled_up_new_tool_bit = tablet->tool_state ^ tablet->prev_tool_state; + tablet->tool_state = 0; } changed = tablet->tool_state ^ tablet->prev_tool_state; @@ -1795,18 +1841,33 @@ tablet_update_tool_state(struct tablet_dispatch *tablet, * events it means the tablet will give us the right * events after all and we can disable our * timer-based proximity out. - * - * We can't do so permanently though, some tablets - * send the correct event sequence occasionally but - * are broken otherwise. */ + if (!tablet->quirks.proximity_out_in_progress) + tablet->quirks.need_to_force_prox_out = false; + libinput_timer_cancel(&tablet->quirks.prox_out_timer); } } -out: tablet->prev_tool_state = tablet->tool_state; + if (doubled_up_new_tool_bit) { + tablet->tool_state = doubled_up_new_tool_bit; + return true; /* need to re-process */ + } + return false; +} + +static struct libinput_tablet_tool * +tablet_get_current_tool(struct tablet_dispatch *tablet) +{ + if (tablet->current_tool.type == LIBINPUT_TOOL_NONE) + return NULL; + + return tablet_get_tool(tablet, + tablet->current_tool.type, + tablet->current_tool.id, + tablet->current_tool.serial); } static void @@ -1815,16 +1876,12 @@ tablet_flush(struct tablet_dispatch *tablet, uint64_t time) { struct libinput_tablet_tool *tool; + bool process_tool_twice; - tablet_update_tool_state(tablet, device, time); - if (tablet->current_tool.type == LIBINPUT_TOOL_NONE) - return; - - tool = tablet_get_tool(tablet, - tablet->current_tool.type, - tablet->current_tool.id, - tablet->current_tool.serial); +reprocess: + process_tool_twice = tablet_update_tool_state(tablet, device, time); + tool = tablet_get_current_tool(tablet); if (!tool) return; /* OOM */ @@ -1855,6 +1912,9 @@ tablet_flush(struct tablet_dispatch *tablet, } tablet_send_events(tablet, tool, device, time); + + if (process_tool_twice) + goto reprocess; } static inline void @@ -1918,11 +1978,18 @@ tablet_toggle_touch_device(struct tablet_dispatch *tablet, static inline void tablet_reset_state(struct tablet_dispatch *tablet) { + struct button_state zero = {0}; + /* Update state */ memcpy(&tablet->prev_button_state, &tablet->button_state, sizeof(tablet->button_state)); tablet_unset_status(tablet, TABLET_TOOL_UPDATED); + + if (memcmp(&tablet->button_state, &zero, sizeof(zero)) == 0) + tablet_unset_status(tablet, TABLET_BUTTONS_DOWN); + else + tablet_set_status(tablet, TABLET_BUTTONS_DOWN); } static void @@ -1944,7 +2011,8 @@ tablet_proximity_out_quirk_timer_func(uint64_t now, void *data) }; struct input_event *e; - if (tablet_has_status(tablet, TABLET_TOOL_IN_CONTACT)) { + if (tablet_has_status(tablet, TABLET_TOOL_IN_CONTACT) || + tablet_has_status(tablet, TABLET_BUTTONS_DOWN)) { tablet_proximity_out_quirk_set_timer(tablet, now); return; } @@ -1957,12 +2025,14 @@ tablet_proximity_out_quirk_timer_func(uint64_t now, void *data) evdev_log_debug(tablet->device, "tablet: forcing proximity after timeout\n"); + tablet->quirks.proximity_out_in_progress = true; ARRAY_FOR_EACH(events, e) { tablet->base.interface->process(&tablet->base, tablet->device, e, now); } + tablet->quirks.proximity_out_in_progress = false; tablet->quirks.proximity_out_forced = true; } @@ -2126,7 +2196,8 @@ tablet_check_initial_proximity(struct evdev_device *device, return; tablet_update_tool(tablet, device, tool, state); - tablet_proximity_out_quirk_set_timer(tablet, libinput_now(li)); + if (tablet->quirks.need_to_force_prox_out) + tablet_proximity_out_quirk_set_timer(tablet, libinput_now(li)); tablet->current_tool.id = libevdev_get_event_value(device->evdev, @@ -2333,6 +2404,7 @@ tablet_init(struct tablet_dispatch *tablet, if (rc != 0) return rc; + evdev_init_sendevents(device, &tablet->base); tablet_init_left_handed(device); for (axis = LIBINPUT_TABLET_TOOL_AXIS_X; @@ -2347,8 +2419,6 @@ tablet_init(struct tablet_dispatch *tablet, /* We always enable the proximity out quirk, but disable it once a device gives us the right event sequence */ tablet->quirks.need_to_force_prox_out = true; - if (evdev_device_has_model_quirk(device, QUIRK_MODEL_WACOM_ISDV4_PEN)) - tablet->quirks.need_to_force_prox_out = false; libinput_timer_init(&tablet->quirks.prox_out_timer, tablet_libinput_context(tablet), diff --git a/src/evdev-tablet.h b/src/evdev-tablet.h index ab95b61..34d5b99 100644 --- a/src/evdev-tablet.h +++ b/src/evdev-tablet.h @@ -37,15 +37,16 @@ enum tablet_status { TABLET_NONE = 0, TABLET_AXES_UPDATED = bit(0), TABLET_BUTTONS_PRESSED = bit(1), - TABLET_BUTTONS_RELEASED = bit(2), - TABLET_TOOL_UPDATED = bit(3), - TABLET_TOOL_IN_CONTACT = bit(4), - TABLET_TOOL_LEAVING_PROXIMITY = bit(5), - TABLET_TOOL_OUT_OF_PROXIMITY = bit(6), - TABLET_TOOL_ENTERING_PROXIMITY = bit(7), - TABLET_TOOL_ENTERING_CONTACT = bit(8), - TABLET_TOOL_LEAVING_CONTACT = bit(9), - TABLET_TOOL_OUT_OF_RANGE = bit(10), + TABLET_BUTTONS_DOWN = bit(2), + TABLET_BUTTONS_RELEASED = bit(3), + TABLET_TOOL_UPDATED = bit(4), + TABLET_TOOL_IN_CONTACT = bit(5), + TABLET_TOOL_LEAVING_PROXIMITY = bit(6), + TABLET_TOOL_OUT_OF_PROXIMITY = bit(7), + TABLET_TOOL_ENTERING_PROXIMITY = bit(8), + TABLET_TOOL_ENTERING_CONTACT = bit(9), + TABLET_TOOL_LEAVING_CONTACT = bit(10), + TABLET_TOOL_OUT_OF_RANGE = bit(11), }; struct button_state { @@ -106,6 +107,9 @@ struct tablet_dispatch { struct libinput_timer prox_out_timer; bool proximity_out_forced; uint64_t last_event_time; + + /* true while injecting BTN_TOOL_PEN events */ + bool proximity_out_in_progress; } quirks; }; diff --git a/src/evdev-totem.c b/src/evdev-totem.c index 6f0a851..f2ddcd0 100644 --- a/src/evdev-totem.c +++ b/src/evdev-totem.c @@ -181,6 +181,10 @@ totem_process_key(struct totem_dispatch *totem, struct input_event *e, uint64_t time) { + /* ignore kernel key repeat */ + if (e->value == 2) + return; + switch(e->code) { case BTN_0: totem->button_state_now = !!e->value; diff --git a/src/evdev.c b/src/evdev.c index 99713f2..40f0726 100644 --- a/src/evdev.c +++ b/src/evdev.c @@ -36,7 +36,6 @@ #include #include #include -#include #include #include "libinput.h" @@ -44,6 +43,7 @@ #include "filter.h" #include "libinput-private.h" #include "quirks.h" +#include "util-input-event.h" #if HAVE_LIBWACOM #include @@ -199,6 +199,34 @@ static void evdev_button_scroll_button(struct evdev_device *device, uint64_t time, int is_press) { + /* Where the button lock is enabled, we wrap the buttons into + their own little state machine and filter out the events. + */ + switch (device->scroll.lock_state) { + case BUTTONSCROLL_LOCK_DISABLED: + break; + case BUTTONSCROLL_LOCK_IDLE: + assert(is_press); + device->scroll.lock_state = BUTTONSCROLL_LOCK_FIRSTDOWN; + evdev_log_debug(device, "scroll lock: first down\n"); + break; /* handle event */ + case BUTTONSCROLL_LOCK_FIRSTDOWN: + assert(!is_press); + device->scroll.lock_state = BUTTONSCROLL_LOCK_FIRSTUP; + evdev_log_debug(device, "scroll lock: first up\n"); + return; /* filter release event */ + case BUTTONSCROLL_LOCK_FIRSTUP: + assert(is_press); + device->scroll.lock_state = BUTTONSCROLL_LOCK_SECONDDOWN; + evdev_log_debug(device, "scroll lock: second down\n"); + return; /* filter press event */ + case BUTTONSCROLL_LOCK_SECONDDOWN: + assert(!is_press); + device->scroll.lock_state = BUTTONSCROLL_LOCK_IDLE; + evdev_log_debug(device, "scroll lock: idle\n"); + break; /* handle event */ + } + if (is_press) { enum timer_flags flags = TIMER_FLAG_NONE; @@ -706,6 +734,56 @@ evdev_scroll_get_default_button(struct libinput_device *device) return 0; } +static enum libinput_config_status +evdev_scroll_set_button_lock(struct libinput_device *device, + enum libinput_config_scroll_button_lock_state state) +{ + struct evdev_device *evdev = evdev_device(device); + + switch (state) { + case LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_DISABLED: + evdev->scroll.want_lock_enabled = false; + break; + case LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_ENABLED: + evdev->scroll.want_lock_enabled = true; + break; + default: + return LIBINPUT_CONFIG_STATUS_INVALID; + } + + evdev->scroll.change_scroll_method(evdev); + + return LIBINPUT_CONFIG_STATUS_SUCCESS; +} + +static enum libinput_config_scroll_button_lock_state +evdev_scroll_get_button_lock(struct libinput_device *device) +{ + struct evdev_device *evdev = evdev_device(device); + + if (evdev->scroll.lock_state == BUTTONSCROLL_LOCK_DISABLED) + return LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_DISABLED; + else + return LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_ENABLED; +} + +static enum libinput_config_scroll_button_lock_state +evdev_scroll_get_default_button_lock(struct libinput_device *device) +{ + return LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_DISABLED; +} + + +void +evdev_set_button_scroll_lock_enabled(struct evdev_device *device, + bool enabled) +{ + if (enabled) + device->scroll.lock_state = BUTTONSCROLL_LOCK_IDLE; + else + device->scroll.lock_state = BUTTONSCROLL_LOCK_DISABLED; +} + void evdev_init_button_scroll(struct evdev_device *device, void (*change_scroll_method)(struct evdev_device *)) @@ -727,6 +805,9 @@ evdev_init_button_scroll(struct evdev_device *device, device->scroll.config.set_button = evdev_scroll_set_button; device->scroll.config.get_button = evdev_scroll_get_button; device->scroll.config.get_default_button = evdev_scroll_get_default_button; + device->scroll.config.set_button_lock = evdev_scroll_set_button_lock; + device->scroll.config.get_button_lock = evdev_scroll_get_button_lock; + device->scroll.config.get_default_button_lock = evdev_scroll_get_default_button_lock; device->base.config.scroll_method = &device->scroll.config; device->scroll.method = evdev_scroll_get_default_method((struct libinput_device *)device); device->scroll.want_method = device->scroll.method; @@ -859,7 +940,7 @@ evdev_print_event(struct evdev_device *device, { static uint32_t offset = 0; static uint32_t last_time = 0; - uint32_t time = us2ms(tv2us(&e->time)); + uint32_t time = us2ms(input_event_time(e)); if (offset == 0) { offset = time; @@ -891,7 +972,7 @@ static inline void evdev_process_event(struct evdev_device *device, struct input_event *e) { struct evdev_dispatch *dispatch = device->dispatch; - uint64_t time = tv2us(&e->time); + uint64_t time = input_event_time(e); #if 0 evdev_print_event(device, e); @@ -937,6 +1018,31 @@ evdev_sync_device(struct evdev_device *device) return rc == -EAGAIN ? 0 : rc; } +static inline void +evdev_note_time_delay(struct evdev_device *device, + const struct input_event *ev) +{ + struct libinput *libinput = evdev_libinput_context(device); + uint32_t tdelta; + + /* if we have a current libinput_dispatch() snapshot, compare our + * event time with the one from the snapshot. If we have more than + * 10ms delay, complain about it. This catches delays in processing + * where there is no steady event flow and thus SYN_DROPPED may not + * get hit by the kernel despite us being too slow. + */ + if (libinput->dispatch_time == 0) + return; + + tdelta = us2ms(libinput->dispatch_time - input_event_time(ev)); + if (tdelta > 10) { + evdev_log_bug_client_ratelimit(device, + &device->delay_warning_limit, + "event processing lagging behind by %dms, your system is too slow\n", + tdelta); + } +} + static void evdev_device_dispatch(void *data) { @@ -944,6 +1050,7 @@ evdev_device_dispatch(void *data) struct libinput *libinput = evdev_libinput_context(device); struct input_event ev; int rc; + bool once = false; /* If the compositor is repainting, this function is called only once * per frame and we have to process all the events available on the @@ -966,6 +1073,10 @@ evdev_device_dispatch(void *data) if (rc == 0) rc = LIBEVDEV_READ_STATUS_SUCCESS; } else if (rc == LIBEVDEV_READ_STATUS_SUCCESS) { + if (!once) { + evdev_note_time_delay(device, &ev); + once = true; + } evdev_device_dispatch_one(device, &ev); } } while (rc == LIBEVDEV_READ_STATUS_SUCCESS); @@ -1195,21 +1306,6 @@ evdev_read_wheel_click_props(struct evdev_device *device) return angles; } -static inline struct wheel_tilt_flags -evdev_read_wheel_tilt_props(struct evdev_device *device) -{ - struct wheel_tilt_flags flags; - - flags.vertical = parse_udev_flag(device, - device->udev_device, - "MOUSE_WHEEL_TILT_VERTICAL"); - - flags.horizontal = parse_udev_flag(device, - device->udev_device, - "MOUSE_WHEEL_TILT_HORIZONTAL"); - return flags; -} - static inline double evdev_get_trackpoint_multiplier(struct evdev_device *device) { @@ -1303,6 +1399,7 @@ evdev_read_model_flags(struct evdev_device *device) #define MODEL(name) { QUIRK_MODEL_##name, EVDEV_MODEL_##name } MODEL(WACOM_TOUCHPAD), MODEL(SYNAPTICS_SERIAL_TOUCHPAD), + MODEL(ALPS_SERIAL_TOUCHPAD), MODEL(LENOVO_T450_TOUCHPAD), MODEL(TRACKBALL), MODEL(APPLE_TOUCHPAD_ONEBUTTON), @@ -1713,8 +1810,6 @@ evdev_configure_device(struct evdev_device *device) evdev_disable_accelerometer_axes(device); } - /* libwacom *adds* TABLET, TOUCHPAD but leaves JOYSTICK in place, so - make sure we only ignore real joystick devices */ if (udev_tags == (EVDEV_UDEV_TAG_INPUT|EVDEV_UDEV_TAG_JOYSTICK)) { evdev_log_info(device, "device is a joystick, ignoring\n"); @@ -1826,15 +1921,20 @@ evdev_configure_device(struct evdev_device *device) if (libevdev_has_event_code(evdev, EV_SW, SW_TABLET_MODE)) { if (evdev_device_has_model_quirk(device, - QUIRK_MODEL_TABLET_MODE_SWITCH_UNRELIABLE)) + QUIRK_MODEL_TABLET_MODE_SWITCH_UNRELIABLE)) { evdev_log_info(device, - "device is an unreliable tablet mode switch.\n"); - else + "device is an unreliable tablet mode switch, filtering events.\n"); + libevdev_disable_event_code(device->evdev, + EV_SW, + SW_TABLET_MODE); + } else { device->tags |= EVDEV_TAG_TABLET_MODE_SWITCH; + device->seat_caps |= EVDEV_DEVICE_SWITCH; + } + } - device->seat_caps |= EVDEV_DEVICE_SWITCH; + if (device->seat_caps & EVDEV_DEVICE_SWITCH) evdev_log_info(device, "device is a switch device\n"); - } } if (device->seat_caps & EVDEV_DEVICE_POINTER && @@ -2120,12 +2220,13 @@ evdev_device_create(struct libinput_seat *seat, device->scroll.direction = 0; device->scroll.wheel_click_angle = evdev_read_wheel_click_props(device); - device->scroll.is_tilt = evdev_read_wheel_tilt_props(device); device->model_flags = evdev_read_model_flags(device); device->dpi = DEFAULT_MOUSE_DPI; /* at most 5 SYN_DROPPED log-messages per 30s */ ratelimit_init(&device->syn_drop_limit, s2us(30), 5); + /* at most 5 "delayed processing" log messages per hour */ + ratelimit_init(&device->delay_warning_limit, s2us(60 * 60), 5); /* at most 5 log-messages per 5s */ ratelimit_init(&device->nonpointer_rel_limit, s2us(5), 5); @@ -2136,11 +2237,8 @@ evdev_device_create(struct libinput_seat *seat, evdev_pre_configure_model_quirks(device); device->dispatch = evdev_configure_device(device); - if (device->dispatch == NULL) { - if (device->seat_caps == 0) - unhandled_device = 1; + if (device->dispatch == NULL || device->seat_caps == 0) goto err; - } device->source = libinput_add_fd(libinput, fd, evdev_device_dispatch, device); @@ -2158,8 +2256,10 @@ evdev_device_create(struct libinput_seat *seat, err: close_restricted(libinput, fd); - if (device) + if (device) { + unhandled_device = device->seat_caps == 0; evdev_device_destroy(device); + } return unhandled_device ? EVDEV_UNHANDLED_DEVICE : NULL; } diff --git a/src/evdev.h b/src/evdev.h index 7de1fea..b0b1b8c 100644 --- a/src/evdev.h +++ b/src/evdev.h @@ -108,6 +108,7 @@ enum evdev_device_model { EVDEV_MODEL_DEFAULT = 0, EVDEV_MODEL_WACOM_TOUCHPAD = bit(1), EVDEV_MODEL_SYNAPTICS_SERIAL_TOUCHPAD = bit(2), + EVDEV_MODEL_ALPS_SERIAL_TOUCHPAD = bit(3), EVDEV_MODEL_LENOVO_T450_TOUCHPAD = bit(4), EVDEV_MODEL_APPLE_TOUCHPAD_ONEBUTTON = bit(5), EVDEV_MODEL_LENOVO_SCROLLPOINT = bit(6), @@ -125,6 +126,14 @@ enum evdev_button_scroll_state { BUTTONSCROLL_SCROLLING, /* have sent scroll events */ }; +enum evdev_button_scroll_lock_state { + BUTTONSCROLL_LOCK_DISABLED, + BUTTONSCROLL_LOCK_IDLE, + BUTTONSCROLL_LOCK_FIRSTDOWN, + BUTTONSCROLL_LOCK_FIRSTUP, + BUTTONSCROLL_LOCK_SECONDDOWN, +}; + enum evdev_debounce_state { /** * Initial state, no debounce but monitoring events @@ -170,6 +179,7 @@ struct evdev_device { double trackpoint_multiplier; /* trackpoint constant multiplier */ bool use_velocity_averaging; /* whether averaging should be applied on velocity calculation */ struct ratelimit syn_drop_limit; /* ratelimit for SYN_DROPPED logging */ + struct ratelimit delay_warning_limit; /* ratelimit for delayd processing logging */ struct ratelimit nonpointer_rel_limit; /* ratelimit for REL_* events from non-pointer devices */ uint32_t model_flags; struct mtdev *mtdev; @@ -223,7 +233,9 @@ struct evdev_device { /* angle per REL_WHEEL click in degrees */ struct wheel_angle wheel_click_angle; - struct wheel_tilt_flags is_tilt; + enum evdev_button_scroll_lock_state lock_state; + bool want_lock_enabled; + bool lock_enabled; } scroll; struct { @@ -499,6 +511,10 @@ evdev_device_has_switch(struct evdev_device *device, enum libinput_switch sw); int +evdev_device_tablet_pad_has_key(struct evdev_device *device, + uint32_t code); + +int evdev_device_tablet_pad_get_num_buttons(struct evdev_device *device); int @@ -557,6 +573,10 @@ void evdev_init_button_scroll(struct evdev_device *device, void (*change_scroll_method)(struct evdev_device *)); +void +evdev_set_button_scroll_lock_enabled(struct evdev_device *device, + bool enabled); + int evdev_update_key_down_count(struct evdev_device *device, int code, @@ -793,12 +813,17 @@ evdev_log_msg_ratelimit(struct evdev_device *device, log_msg_va(evdev_libinput_context(device), priority, buf, args); va_end(args); - if (state == RATELIMIT_THRESHOLD) + if (state == RATELIMIT_THRESHOLD) { + struct human_time ht = to_human_time(ratelimit->interval); evdev_log_msg(device, priority, - "WARNING: log rate limit exceeded (%d msgs per %dms). Discarding future messages.\n", + "WARNING: log rate limit exceeded (%d msgs per %d%s). " + "Discarding future messages.\n", ratelimit->burst, - us2ms(ratelimit->interval)); + ht.value, + ht.unit); + + } } #define evdev_log_debug(d_, ...) evdev_log_msg((d_), LIBINPUT_LOG_PRIORITY_DEBUG, __VA_ARGS__) diff --git a/src/filter-flat.c b/src/filter-flat.c index 1aa0bef..b07542e 100644 --- a/src/filter-flat.c +++ b/src/filter-flat.c @@ -29,8 +29,6 @@ #include #include #include -#include -#include #include "filter.h" #include "libinput-util.h" diff --git a/src/filter-low-dpi.c b/src/filter-low-dpi.c index 9a39f52..ce77c5c 100644 --- a/src/filter-low-dpi.c +++ b/src/filter-low-dpi.c @@ -29,8 +29,6 @@ #include #include #include -#include -#include #include "filter.h" #include "libinput-util.h" diff --git a/src/filter-mouse.c b/src/filter-mouse.c index 89c3919..8d0a205 100644 --- a/src/filter-mouse.c +++ b/src/filter-mouse.c @@ -29,8 +29,6 @@ #include #include #include -#include -#include #include "filter.h" #include "libinput-util.h" diff --git a/src/filter-touchpad-flat.c b/src/filter-touchpad-flat.c new file mode 100644 index 0000000..658d5b5 --- /dev/null +++ b/src/filter-touchpad-flat.c @@ -0,0 +1,125 @@ +/* + * Copyright © 2006-2009 Simon Thum + * Copyright © 2012 Jonas Ådahl + * Copyright © 2014-2015 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include + +#include "filter.h" +#include "libinput-util.h" +#include "filter-private.h" + + +#define TP_MAGIC_SLOWDOWN_FLAT 0.2968 + +struct touchpad_accelerator_flat { + struct motion_filter base; + + double factor; + int dpi; +}; + +static struct normalized_coords +accelerator_filter_touchpad_flat(struct motion_filter *filter, + const struct device_float_coords *unaccelerated, + void *data, uint64_t time) +{ + struct touchpad_accelerator_flat *accel_filter = + (struct touchpad_accelerator_flat *)filter; + double factor; /* unitless factor */ + struct normalized_coords accelerated; + + /* You want flat acceleration, you get flat acceleration for the + * device */ + factor = accel_filter->factor; + accelerated.x = TP_MAGIC_SLOWDOWN_FLAT * factor * unaccelerated->x; + accelerated.y = TP_MAGIC_SLOWDOWN_FLAT * factor * unaccelerated->y; + + return accelerated; +} + +static struct normalized_coords +accelerator_filter_noop_touchpad_flat(struct motion_filter *filter, + const struct device_float_coords *unaccelerated, + void *data, uint64_t time) +{ + struct touchpad_accelerator_flat *accel = + (struct touchpad_accelerator_flat *) filter; + struct normalized_coords normalized; + + normalized = normalize_for_dpi(unaccelerated, accel->dpi); + normalized.x = TP_MAGIC_SLOWDOWN_FLAT * normalized.x; + normalized.y = TP_MAGIC_SLOWDOWN_FLAT * normalized.y; + + return normalized; +} + +static bool +accelerator_set_speed_touchpad_flat(struct motion_filter *filter, + double speed_adjustment) +{ + struct touchpad_accelerator_flat *accel_filter = + (struct touchpad_accelerator_flat *)filter; + + assert(speed_adjustment >= -1.0 && speed_adjustment <= 1.0); + + accel_filter->factor = max(0.005, 1 + speed_adjustment); + filter->speed_adjustment = speed_adjustment; + + return true; +} + +static void +accelerator_destroy_touchpad_flat(struct motion_filter *filter) +{ + struct touchpad_accelerator_flat *accel = + (struct touchpad_accelerator_flat *) filter; + + free(accel); +} + +struct motion_filter_interface accelerator_interface_touchpad_flat = { + .type = LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT, + .filter = accelerator_filter_touchpad_flat, + .filter_constant = accelerator_filter_noop_touchpad_flat, + .restart = NULL, + .destroy = accelerator_destroy_touchpad_flat, + .set_speed = accelerator_set_speed_touchpad_flat, +}; + +struct motion_filter * +create_pointer_accelerator_filter_touchpad_flat(int dpi) +{ + struct touchpad_accelerator_flat *filter; + + filter = zalloc(sizeof *filter); + filter->base.interface = &accelerator_interface_touchpad_flat; + filter->dpi = dpi; + + return &filter->base; +} diff --git a/src/filter-touchpad-x230.c b/src/filter-touchpad-x230.c index d4cc3e1..a39e0ba 100644 --- a/src/filter-touchpad-x230.c +++ b/src/filter-touchpad-x230.c @@ -26,11 +26,8 @@ #include "config.h" #include -#include #include #include -#include -#include #include "filter.h" #include "libinput-util.h" diff --git a/src/filter-touchpad.c b/src/filter-touchpad.c index 9f833a0..0a9fdc2 100644 --- a/src/filter-touchpad.c +++ b/src/filter-touchpad.c @@ -26,10 +26,8 @@ #include "config.h" #include -#include #include #include -#include #include #include "filter.h" diff --git a/src/filter-trackpoint.c b/src/filter-trackpoint.c index c85a082..c068661 100644 --- a/src/filter-trackpoint.c +++ b/src/filter-trackpoint.c @@ -29,7 +29,6 @@ #include #include #include -#include #include #include "filter.h" diff --git a/src/filter.c b/src/filter.c index ef081dd..6d32065 100644 --- a/src/filter.c +++ b/src/filter.c @@ -29,7 +29,6 @@ #include #include #include -#include #include #include "filter.h" diff --git a/src/filter.h b/src/filter.h index 177acbd..5c1bdcc 100644 --- a/src/filter.h +++ b/src/filter.h @@ -120,6 +120,9 @@ create_pointer_accelerator_filter_touchpad(int dpi, bool use_velocity_averaging); struct motion_filter * +create_pointer_accelerator_filter_touchpad_flat(int dpi); + +struct motion_filter * create_pointer_accelerator_filter_lenovo_x230(int dpi, bool use_velocity_averaging); struct motion_filter * diff --git a/src/libinput-private.h b/src/libinput-private.h index 1950e66..e50eb95 100644 --- a/src/libinput-private.h +++ b/src/libinput-private.h @@ -41,12 +41,6 @@ #include "libinput-util.h" #include "libinput-version.h" -#if LIBINPUT_VERSION_MICRO >= 90 -#define HTTP_DOC_LINK "https://wayland.freedesktop.org/libinput/doc/latest/" -#else -#define HTTP_DOC_LINK "https://wayland.freedesktop.org/libinput/doc/" LIBINPUT_VERSION "/" -#endif - struct libinput_source; /* A coordinate pair in device coordinates */ @@ -117,11 +111,6 @@ struct phys_ellipsis { double minor; }; -/* A pair of tilt flags */ -struct wheel_tilt_flags { - bool vertical, horizontal; -}; - struct libinput_interface_backend { int (*resume)(struct libinput *libinput); void (*suspend)(struct libinput *libinput); @@ -162,6 +151,7 @@ struct libinput { struct list device_group_list; uint64_t last_event_time; + uint64_t dispatch_time; bool quirks_initialized; struct quirks_context *quirks; @@ -272,6 +262,10 @@ struct libinput_device_config_scroll_method { uint32_t button); uint32_t (*get_button)(struct libinput_device *device); uint32_t (*get_default_button)(struct libinput_device *device); + enum libinput_config_status (*set_button_lock)(struct libinput_device *device, + enum libinput_config_scroll_button_lock_state); + enum libinput_config_scroll_button_lock_state (*get_button_lock)(struct libinput_device *device); + enum libinput_config_scroll_button_lock_state (*get_default_button_lock)(struct libinput_device *device); }; struct libinput_device_config_click_method { @@ -685,6 +679,11 @@ tablet_pad_notify_strip(struct libinput_device *device, enum libinput_tablet_pad_strip_axis_source source, struct libinput_tablet_pad_mode_group *group); void +tablet_pad_notify_key(struct libinput_device *device, + uint64_t time, + int32_t key, + enum libinput_key_state state); +void switch_notify_toggle(struct libinput_device *device, uint64_t time, enum libinput_switch sw, diff --git a/src/libinput-util.h b/src/libinput-util.h index 680e724..76da5ae 100644 --- a/src/libinput-util.h +++ b/src/libinput-util.h @@ -31,29 +31,18 @@ #warning "libinput relies on assert(). #defining NDEBUG is not recommended" #endif -#include -#include -#include -#include -#ifdef HAVE_LOCALE_H -#include -#endif -#ifdef HAVE_XLOCALE_H -#include -#endif -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include "libinput.h" +#include "util-bits.h" +#include "util-macros.h" +#include "util-list.h" +#include "util-matrix.h" +#include "util-strings.h" +#include "util-ratelimit.h" +#include "util-prop-parsers.h" +#include "util-time.h" + #define VENDOR_ID_APPLE 0x5ac #define VENDOR_ID_CHICONY 0x4f2 #define VENDOR_ID_LOGITECH 0x46d @@ -68,637 +57,13 @@ #define DEFAULT_MOUSE_DPI 1000 #define DEFAULT_TRACKPOINT_SENSITIVITY 128 -#define ANSI_HIGHLIGHT "\x1B[0;1;39m" -#define ANSI_RED "\x1B[0;31m" -#define ANSI_GREEN "\x1B[0;32m" -#define ANSI_YELLOW "\x1B[0;33m" -#define ANSI_BLUE "\x1B[0;34m" -#define ANSI_MAGENTA "\x1B[0;35m" -#define ANSI_CYAN "\x1B[0;36m" -#define ANSI_BRIGHT_RED "\x1B[0;31;1m" -#define ANSI_BRIGHT_GREEN "\x1B[0;32;1m" -#define ANSI_BRIGHT_YELLOW "\x1B[0;33;1m" -#define ANSI_BRIGHT_BLUE "\x1B[0;34;1m" -#define ANSI_BRIGHT_MAGENTA "\x1B[0;35;1m" -#define ANSI_BRIGHT_CYAN "\x1B[0;36;1m" -#define ANSI_NORMAL "\x1B[0m" - -#define CASE_RETURN_STRING(a) case a: return #a - -#define bit(x_) (1UL << (x_)) -/* - * This list data structure is a verbatim copy from wayland-util.h from the - * Wayland project; except that wl_ prefix has been removed. - */ - -struct list { - struct list *prev; - struct list *next; -}; - -void list_init(struct list *list); -void list_insert(struct list *list, struct list *elm); -void list_append(struct list *list, struct list *elm); -void list_remove(struct list *elm); -bool list_empty(const struct list *list); - -#define container_of(ptr, type, member) \ - (__typeof__(type) *)((char *)(ptr) - \ - offsetof(__typeof__(type), member)) - -#define list_first_entry(head, pos, member) \ - container_of((head)->next, __typeof__(*pos), member) - -#define list_for_each(pos, head, member) \ - for (pos = 0, pos = list_first_entry(head, pos, member); \ - &pos->member != (head); \ - pos = list_first_entry(&pos->member, pos, member)) - -#define list_for_each_safe(pos, tmp, head, member) \ - for (pos = 0, tmp = 0, \ - pos = list_first_entry(head, pos, member), \ - tmp = list_first_entry(&pos->member, tmp, member); \ - &pos->member != (head); \ - pos = tmp, \ - tmp = list_first_entry(&pos->member, tmp, member)) - -#define NBITS(b) (b * 8) -#define LONG_BITS (sizeof(long) * 8) -#define NLONGS(x) (((x) + LONG_BITS - 1) / LONG_BITS) -#define ARRAY_LENGTH(a) (sizeof (a) / sizeof (a)[0]) -#define ARRAY_FOR_EACH(_arr, _elem) \ - for (size_t _i = 0; _i < ARRAY_LENGTH(_arr) && (_elem = &_arr[_i]); _i++) - -#define min(a, b) (((a) < (b)) ? (a) : (b)) -#define max(a, b) (((a) > (b)) ? (a) : (b)) -#define streq(s1, s2) (strcmp((s1), (s2)) == 0) -#define strneq(s1, s2, n) (strncmp((s1), (s2), (n)) == 0) - -#define NCHARS(x) ((size_t)(((x) + 7) / 8)) - -#ifdef DEBUG_TRACE -#define debug_trace(...) \ +#define trace(...) \ do { \ - printf("%s:%d %s() - ", __FILE__, __LINE__, __func__); \ + printf("%s() - \033[0;31m", __func__); \ printf(__VA_ARGS__); \ + printf("\033[0m"); \ } while (0) -#else -#define debug_trace(...) { } -#endif #define LIBINPUT_EXPORT __attribute__ ((visibility("default"))) -static inline void * -zalloc(size_t size) -{ - void *p; - - /* We never need to alloc anything more than 1,5 MB so we can assume - * if we ever get above that something's going wrong */ - if (size > 1536 * 1024) - assert(!"bug: internal malloc size limit exceeded"); - - p = calloc(1, size); - if (!p) - abort(); - - return p; -} - -/** - * strdup guaranteed to succeed. If the input string is NULL, the output - * string is NULL. If the input string is a string pointer, we strdup or - * abort on failure. - */ -static inline char* -safe_strdup(const char *str) -{ - char *s; - - if (!str) - return NULL; - - s = strdup(str); - if (!s) - abort(); - return s; -} - -/* This bitfield helper implementation is taken from from libevdev-util.h, - * except that it has been modified to work with arrays of unsigned chars - */ - -static inline bool -bit_is_set(const unsigned char *array, int bit) -{ - return !!(array[bit / 8] & (1 << (bit % 8))); -} - - static inline void -set_bit(unsigned char *array, int bit) -{ - array[bit / 8] |= (1 << (bit % 8)); -} - - static inline void -clear_bit(unsigned char *array, int bit) -{ - array[bit / 8] &= ~(1 << (bit % 8)); -} - -static inline void -msleep(unsigned int ms) -{ - usleep(ms * 1000); -} - -static inline bool -long_bit_is_set(const unsigned long *array, int bit) -{ - return !!(array[bit / LONG_BITS] & (1ULL << (bit % LONG_BITS))); -} - -static inline void -long_set_bit(unsigned long *array, int bit) -{ - array[bit / LONG_BITS] |= (1ULL << (bit % LONG_BITS)); -} - -static inline void -long_clear_bit(unsigned long *array, int bit) -{ - array[bit / LONG_BITS] &= ~(1ULL << (bit % LONG_BITS)); -} - -static inline void -long_set_bit_state(unsigned long *array, int bit, int state) -{ - if (state) - long_set_bit(array, bit); - else - long_clear_bit(array, bit); -} - -static inline bool -long_any_bit_set(unsigned long *array, size_t size) -{ - unsigned long i; - - assert(size > 0); - - for (i = 0; i < size; i++) - if (array[i] != 0) - return true; - return false; -} - -static inline double -deg2rad(int degree) -{ - return M_PI * degree / 180.0; -} - -struct matrix { - float val[3][3]; /* [row][col] */ -}; - -static inline void -matrix_init_identity(struct matrix *m) -{ - memset(m, 0, sizeof(*m)); - m->val[0][0] = 1; - m->val[1][1] = 1; - m->val[2][2] = 1; -} - -static inline void -matrix_from_farray6(struct matrix *m, const float values[6]) -{ - matrix_init_identity(m); - m->val[0][0] = values[0]; - m->val[0][1] = values[1]; - m->val[0][2] = values[2]; - m->val[1][0] = values[3]; - m->val[1][1] = values[4]; - m->val[1][2] = values[5]; -} - -static inline void -matrix_init_scale(struct matrix *m, float sx, float sy) -{ - matrix_init_identity(m); - m->val[0][0] = sx; - m->val[1][1] = sy; -} - -static inline void -matrix_init_translate(struct matrix *m, float x, float y) -{ - matrix_init_identity(m); - m->val[0][2] = x; - m->val[1][2] = y; -} - -static inline void -matrix_init_rotate(struct matrix *m, int degrees) -{ - double s, c; - - s = sin(deg2rad(degrees)); - c = cos(deg2rad(degrees)); - - matrix_init_identity(m); - m->val[0][0] = c; - m->val[0][1] = -s; - m->val[1][0] = s; - m->val[1][1] = c; -} - -static inline bool -matrix_is_identity(const struct matrix *m) -{ - return (m->val[0][0] == 1 && - m->val[0][1] == 0 && - m->val[0][2] == 0 && - m->val[1][0] == 0 && - m->val[1][1] == 1 && - m->val[1][2] == 0 && - m->val[2][0] == 0 && - m->val[2][1] == 0 && - m->val[2][2] == 1); -} - -static inline void -matrix_mult(struct matrix *dest, - const struct matrix *m1, - const struct matrix *m2) -{ - struct matrix m; /* allow for dest == m1 or dest == m2 */ - int row, col, i; - - for (row = 0; row < 3; row++) { - for (col = 0; col < 3; col++) { - double v = 0; - for (i = 0; i < 3; i++) { - v += m1->val[row][i] * m2->val[i][col]; - } - m.val[row][col] = v; - } - } - - memcpy(dest, &m, sizeof(m)); -} - -static inline void -matrix_mult_vec(const struct matrix *m, int *x, int *y) -{ - int tx, ty; - - tx = *x * m->val[0][0] + *y * m->val[0][1] + m->val[0][2]; - ty = *x * m->val[1][0] + *y * m->val[1][1] + m->val[1][2]; - - *x = tx; - *y = ty; -} - -static inline void -matrix_to_farray6(const struct matrix *m, float out[6]) -{ - out[0] = m->val[0][0]; - out[1] = m->val[0][1]; - out[2] = m->val[0][2]; - out[3] = m->val[1][0]; - out[4] = m->val[1][1]; - out[5] = m->val[1][2]; -} - -static inline void -matrix_to_relative(struct matrix *dest, const struct matrix *src) -{ - matrix_init_identity(dest); - dest->val[0][0] = src->val[0][0]; - dest->val[0][1] = src->val[0][1]; - dest->val[1][0] = src->val[1][0]; - dest->val[1][1] = src->val[1][1]; -} - -/** - * Simple wrapper for asprintf that ensures the passed in-pointer is set - * to NULL upon error. - * The standard asprintf() call does not guarantee the passed in pointer - * will be NULL'ed upon failure, whereas this wrapper does. - * - * @param strp pointer to set to newly allocated string. - * This pointer should be passed to free() to release when done. - * @param fmt the format string to use for printing. - * @return The number of bytes printed (excluding the null byte terminator) - * upon success or -1 upon failure. In the case of failure the pointer is set - * to NULL. - */ -LIBINPUT_ATTRIBUTE_PRINTF(2, 3) -static inline int -xasprintf(char **strp, const char *fmt, ...) -{ - int rc = 0; - va_list args; - - va_start(args, fmt); - rc = vasprintf(strp, fmt, args); - va_end(args); - if ((rc == -1) && strp) - *strp = NULL; - - return rc; -} - -enum ratelimit_state { - RATELIMIT_EXCEEDED, - RATELIMIT_THRESHOLD, - RATELIMIT_PASS, -}; - -struct ratelimit { - uint64_t interval; - uint64_t begin; - unsigned int burst; - unsigned int num; -}; - -void ratelimit_init(struct ratelimit *r, uint64_t ival_ms, unsigned int burst); -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); -int parse_mouse_wheel_click_count_property(const char *prop); -bool parse_dimension_property(const char *prop, size_t *width, size_t *height); -bool parse_calibration_property(const char *prop, float calibration[6]); -bool parse_range_property(const char *prop, int *hi, int *lo); -#define EVENT_CODE_UNDEFINED 0xffff -bool parse_evcode_property(const char *prop, struct input_event *events, size_t *nevents); - -enum tpkbcombo_layout { - TPKBCOMBO_LAYOUT_UNKNOWN, - TPKBCOMBO_LAYOUT_BELOW, -}; -bool parse_tpkbcombo_layout_poperty(const char *prop, - enum tpkbcombo_layout *layout); - -enum switch_reliability { - RELIABILITY_UNKNOWN, - RELIABILITY_RELIABLE, - RELIABILITY_WRITE_OPEN, -}; - -bool -parse_switch_reliability_property(const char *prop, - enum switch_reliability *reliability); - -static inline uint64_t -us(uint64_t us) -{ - return us; -} - -static inline uint64_t -ns2us(uint64_t ns) -{ - return us(ns / 1000); -} - -static inline uint64_t -ms2us(uint64_t ms) -{ - return us(ms * 1000); -} - -static inline uint64_t -s2us(uint64_t s) -{ - return ms2us(s * 1000); -} - -static inline uint32_t -us2ms(uint64_t us) -{ - return (uint32_t)(us / 1000); -} - -static inline uint64_t -tv2us(const struct timeval *tv) -{ - return s2us(tv->tv_sec) + tv->tv_usec; -} - -static inline struct timeval -us2tv(uint64_t time) -{ - struct timeval tv; - - tv.tv_sec = time / ms2us(1000); - tv.tv_usec = time % ms2us(1000); - - return tv; -} - -static inline bool -safe_atoi_base(const char *str, int *val, int base) -{ - char *endptr; - long v; - - assert(base == 10 || base == 16 || base == 8); - - errno = 0; - v = strtol(str, &endptr, base); - if (errno > 0) - return false; - if (str == endptr) - return false; - if (*str != '\0' && *endptr != '\0') - return false; - - if (v > INT_MAX || v < INT_MIN) - return false; - - *val = v; - return true; -} - -static inline bool -safe_atoi(const char *str, int *val) -{ - return safe_atoi_base(str, val, 10); -} - -static inline bool -safe_atou_base(const char *str, unsigned int *val, int base) -{ - char *endptr; - unsigned long v; - - assert(base == 10 || base == 16 || base == 8); - - errno = 0; - v = strtoul(str, &endptr, base); - if (errno > 0) - return false; - if (str == endptr) - return false; - if (*str != '\0' && *endptr != '\0') - return false; - - if ((long)v < 0) - return false; - - *val = v; - return true; -} - -static inline bool -safe_atou(const char *str, unsigned int *val) -{ - return safe_atou_base(str, val, 10); -} - -static inline bool -safe_atod(const char *str, double *val) -{ - char *endptr; - double v; -#ifdef HAVE_LOCALE_H - locale_t c_locale; -#endif - size_t slen = strlen(str); - - /* We don't have a use-case where we want to accept hex for a double - * or any of the other values strtod can parse */ - for (size_t i = 0; i < slen; i++) { - char c = str[i]; - - if (isdigit(c)) - continue; - switch(c) { - case '+': - case '-': - case '.': - break; - default: - return false; - } - } - -#ifdef HAVE_LOCALE_H - /* 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 false; - - errno = 0; - v = strtod_l(str, &endptr, c_locale); - freelocale(c_locale); -#else - /* No locale support in provided libc, assume it already uses '.' */ - errno = 0; - v = strtod(str, &endptr); -#endif - if (errno > 0) - return false; - if (str == endptr) - return false; - if (*str != '\0' && *endptr != '\0') - return false; - if (v != 0.0 && !isnormal(v)) - return false; - - *val = v; - return true; -} - -char **strv_from_string(const char *string, const char *separator); -char *strv_join(char **strv, const char *separator); - -static inline void -strv_free(char **strv) { - char **s = strv; - - if (!strv) - return; - - while (*s != NULL) { - free(*s); - *s = (char*)0x1; /* detect use-after-free */ - s++; - } - - free (strv); -} - -struct key_value_str{ - char *key; - char *value; -}; - -struct key_value_double { - double key; - double value; -}; - -static inline ssize_t -kv_double_from_string(const char *string, - const char *pair_separator, - const char *kv_separator, - struct key_value_double **result_out) - -{ - char **pairs; - char **pair; - struct key_value_double *result = NULL; - ssize_t npairs = 0; - unsigned int idx = 0; - - if (!pair_separator || pair_separator[0] == '\0' || - !kv_separator || kv_separator[0] == '\0') - return -1; - - pairs = strv_from_string(string, pair_separator); - if (!pairs) - return -1; - - for (pair = pairs; *pair; pair++) - npairs++; - - if (npairs == 0) - goto error; - - result = zalloc(npairs * sizeof *result); - - for (pair = pairs; *pair; pair++) { - char **kv = strv_from_string(*pair, kv_separator); - double k, v; - - if (!kv || !kv[0] || !kv[1] || kv[2] || - !safe_atod(kv[0], &k) || - !safe_atod(kv[1], &v)) { - strv_free(kv); - goto error; - } - - result[idx].key = k; - result[idx].value = v; - idx++; - - strv_free(kv); - } - - strv_free(pairs); - - *result_out = result; - - return npairs; - -error: - strv_free(pairs); - free(result); - return -1; -} #endif /* LIBINPUT_UTIL_H */ diff --git a/src/libinput.c b/src/libinput.c index 6d00a00..7a53598 100644 --- a/src/libinput.c +++ b/src/libinput.c @@ -130,6 +130,7 @@ event_type_to_str(enum libinput_event_type type) CASE_RETURN_STRING(LIBINPUT_EVENT_TABLET_PAD_BUTTON); CASE_RETURN_STRING(LIBINPUT_EVENT_TABLET_PAD_RING); CASE_RETURN_STRING(LIBINPUT_EVENT_TABLET_PAD_STRIP); + CASE_RETURN_STRING(LIBINPUT_EVENT_TABLET_PAD_KEY); CASE_RETURN_STRING(LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN); CASE_RETURN_STRING(LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE); CASE_RETURN_STRING(LIBINPUT_EVENT_GESTURE_SWIPE_END); @@ -219,6 +220,10 @@ struct libinput_event_tablet_pad { enum libinput_button_state state; } button; struct { + uint32_t code; + enum libinput_key_state state; + } key; + struct { enum libinput_tablet_pad_ring_axis_source source; double position; int number; @@ -425,7 +430,8 @@ libinput_event_get_tablet_pad_event(struct libinput_event *event) NULL, LIBINPUT_EVENT_TABLET_PAD_RING, LIBINPUT_EVENT_TABLET_PAD_STRIP, - LIBINPUT_EVENT_TABLET_PAD_BUTTON); + LIBINPUT_EVENT_TABLET_PAD_BUTTON, + LIBINPUT_EVENT_TABLET_PAD_KEY); return (struct libinput_event_tablet_pad *) event; } @@ -1914,7 +1920,8 @@ libinput_event_tablet_tool_destroy(struct libinput_event_tablet_tool *event) static void libinput_event_tablet_pad_destroy(struct libinput_event_tablet_pad *event) { - libinput_tablet_pad_mode_group_unref(event->mode_group); + if (event->base.type != LIBINPUT_EVENT_TABLET_PAD_KEY) + libinput_tablet_pad_mode_group_unref(event->mode_group); } LIBINPUT_EXPORT void @@ -1934,6 +1941,7 @@ libinput_event_destroy(struct libinput_event *event) case LIBINPUT_EVENT_TABLET_PAD_RING: case LIBINPUT_EVENT_TABLET_PAD_STRIP: case LIBINPUT_EVENT_TABLET_PAD_BUTTON: + case LIBINPUT_EVENT_TABLET_PAD_KEY: libinput_event_tablet_pad_destroy( libinput_event_get_tablet_pad_event(event)); break; @@ -2092,10 +2100,19 @@ libinput_get_fd(struct libinput *libinput) LIBINPUT_EXPORT int libinput_dispatch(struct libinput *libinput) { + static uint8_t take_time_snapshot; struct libinput_source *source; struct epoll_event ep[32]; int i, count; + /* Every 10 calls to libinput_dispatch() we take the current time so + * we can check the delay between our current time and the event + * timestamps */ + if ((++take_time_snapshot % 10) == 0) + libinput->dispatch_time = libinput_now(libinput); + else if (libinput->dispatch_time) + libinput->dispatch_time = 0; + count = epoll_wait(libinput->epoll_fd, ep, ARRAY_LENGTH(ep), 0); if (count < 0) return -errno; @@ -2767,6 +2784,28 @@ tablet_pad_notify_strip(struct libinput_device *device, &strip_event->base); } +void +tablet_pad_notify_key(struct libinput_device *device, + uint64_t time, + int32_t key, + enum libinput_key_state state) +{ + struct libinput_event_tablet_pad *key_event; + + key_event = zalloc(sizeof *key_event); + + *key_event = (struct libinput_event_tablet_pad) { + .time = time, + .key.code = key, + .key.state = state, + }; + + post_device_event(device, + time, + LIBINPUT_EVENT_TABLET_PAD_KEY, + &key_event->base); +} + static void gesture_notify(struct libinput_device *device, uint64_t time, @@ -3116,6 +3155,13 @@ libinput_device_switch_has_switch(struct libinput_device *device, } LIBINPUT_EXPORT int +libinput_device_tablet_pad_has_key(struct libinput_device *device, uint32_t code) +{ + return evdev_device_tablet_pad_has_key((struct evdev_device *)device, + code); +} + +LIBINPUT_EXPORT int libinput_device_tablet_pad_get_num_buttons(struct libinput_device *device) { return evdev_device_tablet_pad_get_num_buttons((struct evdev_device *)device); @@ -3418,6 +3464,28 @@ libinput_event_tablet_pad_get_button_state(struct libinput_event_tablet_pad *eve return event->button.state; } +LIBINPUT_EXPORT uint32_t +libinput_event_tablet_pad_get_key(struct libinput_event_tablet_pad *event) +{ + require_event_type(libinput_event_get_context(&event->base), + event->base.type, + 0, + LIBINPUT_EVENT_TABLET_PAD_KEY); + + return event->key.code; +} + +LIBINPUT_EXPORT enum libinput_key_state +libinput_event_tablet_pad_get_key_state(struct libinput_event_tablet_pad *event) +{ + require_event_type(libinput_event_get_context(&event->base), + event->base.type, + LIBINPUT_KEY_STATE_RELEASED, + LIBINPUT_EVENT_TABLET_PAD_KEY); + + return event->key.state; +} + LIBINPUT_EXPORT unsigned int libinput_event_tablet_pad_get_mode(struct libinput_event_tablet_pad *event) { @@ -3452,7 +3520,8 @@ libinput_event_tablet_pad_get_time(struct libinput_event_tablet_pad *event) 0, LIBINPUT_EVENT_TABLET_PAD_RING, LIBINPUT_EVENT_TABLET_PAD_STRIP, - LIBINPUT_EVENT_TABLET_PAD_BUTTON); + LIBINPUT_EVENT_TABLET_PAD_BUTTON, + LIBINPUT_EVENT_TABLET_PAD_KEY); return us2ms(event->time); } @@ -3465,7 +3534,8 @@ libinput_event_tablet_pad_get_time_usec(struct libinput_event_tablet_pad *event) 0, LIBINPUT_EVENT_TABLET_PAD_RING, LIBINPUT_EVENT_TABLET_PAD_STRIP, - LIBINPUT_EVENT_TABLET_PAD_BUTTON); + LIBINPUT_EVENT_TABLET_PAD_BUTTON, + LIBINPUT_EVENT_TABLET_PAD_KEY); return event->time; } @@ -3478,7 +3548,8 @@ libinput_event_tablet_pad_get_base_event(struct libinput_event_tablet_pad *event NULL, LIBINPUT_EVENT_TABLET_PAD_RING, LIBINPUT_EVENT_TABLET_PAD_STRIP, - LIBINPUT_EVENT_TABLET_PAD_BUTTON); + LIBINPUT_EVENT_TABLET_PAD_BUTTON, + LIBINPUT_EVENT_TABLET_PAD_KEY); return &event->base; } @@ -4149,6 +4220,45 @@ libinput_device_config_scroll_get_default_button(struct libinput_device *device) return device->config.scroll_method->get_default_button(device); } +LIBINPUT_EXPORT enum libinput_config_status +libinput_device_config_scroll_set_button_lock(struct libinput_device *device, + enum libinput_config_scroll_button_lock_state state) +{ + if ((libinput_device_config_scroll_get_methods(device) & + LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN) == 0) + return LIBINPUT_CONFIG_STATUS_UNSUPPORTED; + + switch (state) { + case LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_ENABLED: + case LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_DISABLED: + break; + default: + return LIBINPUT_CONFIG_STATUS_INVALID; + } + + return device->config.scroll_method->set_button_lock(device, state); +} + +LIBINPUT_EXPORT enum libinput_config_scroll_button_lock_state +libinput_device_config_scroll_get_button_lock(struct libinput_device *device) +{ + if ((libinput_device_config_scroll_get_methods(device) & + LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN) == 0) + return LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_DISABLED; + + return device->config.scroll_method->get_button_lock(device); +} + +LIBINPUT_EXPORT enum libinput_config_scroll_button_lock_state +libinput_device_config_scroll_get_default_button_lock(struct libinput_device *device) +{ + if ((libinput_device_config_scroll_get_methods(device) & + LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN) == 0) + return LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_DISABLED; + + return device->config.scroll_method->get_default_button_lock(device); +} + LIBINPUT_EXPORT int libinput_device_config_dwt_is_available(struct libinput_device *device) { diff --git a/src/libinput.h b/src/libinput.h index bde76f8..9570648 100644 --- a/src/libinput.h +++ b/src/libinput.h @@ -272,6 +272,10 @@ enum libinput_pointer_axis_source { * The event is caused by the tilting of a mouse wheel rather than * its rotation. This method is commonly used on mice without * separate horizontal scroll wheels. + * + * @deprecated This axis source is deprecated as of libinput 1.16. + * It was never used by any device before libinput 1.16. All wheel + * tilt devices use @ref LIBINPUT_POINTER_AXIS_SOURCE_WHEEL instead. */ LIBINPUT_POINTER_AXIS_SOURCE_WHEEL_TILT, }; @@ -835,7 +839,7 @@ enum libinput_event_type { * This event is not to be confused with the button events emitted * by the tablet pad. See @ref LIBINPUT_EVENT_TABLET_PAD_BUTTON. * - * @see LIBINPUT_EVENT_TABLET_BUTTON + * @see LIBINPUT_EVENT_TABLET_PAD_BUTTON * * @since 1.2 */ @@ -845,6 +849,12 @@ enum libinput_event_type { * A button pressed on a device with the @ref * LIBINPUT_DEVICE_CAP_TABLET_PAD capability. * + * A button differs from @ref LIBINPUT_EVENT_TABLET_PAD_KEY in that + * buttons are sequentially indexed from 0 and do not carry any + * other information. Keys have a specific functionality assigned + * to them. The key code thus carries a semantic meaning, a button + * number does not. + * * This event is not to be confused with the button events emitted * by tools on a tablet (@ref LIBINPUT_EVENT_TABLET_TOOL_BUTTON). * @@ -867,6 +877,19 @@ enum libinput_event_type { */ LIBINPUT_EVENT_TABLET_PAD_STRIP, + /** + * A key pressed on a device with the @ref + * LIBINPUT_DEVICE_CAP_TABLET_PAD capability. + * + * A key differs from @ref LIBINPUT_EVENT_TABLET_PAD_BUTTON in that + * keys have a specific functionality assigned to them (buttons are + * sequentially ordered). The key code thus carries a semantic + * meaning, a button number does not. + * + * @since 1.15 + */ + LIBINPUT_EVENT_TABLET_PAD_KEY, + LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN = 800, LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE, LIBINPUT_EVENT_GESTURE_SWIPE_END, @@ -1453,14 +1476,8 @@ libinput_event_pointer_get_axis_value(struct libinput_event_pointer *event, * The coordinate system is identical to the cursor movement, i.e. a * scroll value of 1 represents the equivalent relative motion of 1. * - * If the source is @ref LIBINPUT_POINTER_AXIS_SOURCE_WHEEL_TILT, no - * terminating event is guaranteed (though it may happen). - * Scrolling is in discrete steps and there is no physical equivalent for - * the value returned here. For backwards compatibility, the value returned - * by this function is identical to a single mouse wheel rotation by this - * device (see the documentation for @ref LIBINPUT_POINTER_AXIS_SOURCE_WHEEL - * above). Callers should not use this value but instead exclusively refer - * to the value returned by libinput_event_pointer_get_axis_value_discrete(). + * @deprecated The source @ref LIBINPUT_POINTER_AXIS_SOURCE_WHEEL_TILT is + * deprecated as of libinput 1.16. No device has ever sent this source. * * For pointer events that are not of type @ref LIBINPUT_EVENT_POINTER_AXIS, * this function returns 0. @@ -3065,6 +3082,44 @@ libinput_event_tablet_pad_get_button_state(struct libinput_event_tablet_pad *eve /** * @ingroup event_tablet_pad * + * Return the key code that triggered this event, e.g. KEY_CONTROLPANEL. The + * list of key codes is defined in linux/input-event-codes.h. + * + * For events that are not of type @ref LIBINPUT_EVENT_TABLET_PAD_KEY, + * this function returns 0. + * + * @note It is an application bug to call this function for events other than + * @ref LIBINPUT_EVENT_TABLET_PAD_KEY. For other events, this function + * returns 0. + * + * @param event The libinput tablet pad event + * @return the key code triggering this event + * + * @since 1.15 + */ +uint32_t +libinput_event_tablet_pad_get_key(struct libinput_event_tablet_pad *event); + +/** + * @ingroup event_tablet_pad + * + * Return the key state of the event. + * + * @note It is an application bug to call this function for events other than + * @ref LIBINPUT_EVENT_TABLET_PAD_KEY. For other events, this function + * returns 0. + * + * @param event The libinput tablet pad event + * @return the key state triggering this event + * + * @since 1.15 + */ +enum libinput_key_state +libinput_event_tablet_pad_get_key_state(struct libinput_event_tablet_pad *event); + +/** + * @ingroup event_tablet_pad + * * Returns the mode the button, ring, or strip that triggered this event is * in, at the time of the event. * @@ -3072,6 +3127,9 @@ libinput_event_tablet_pad_get_button_state(struct libinput_event_tablet_pad *eve * visual feedback like LEDs on the pad. Mode indices start at 0, a device * that does not support modes always returns 0. * + * @note Pad keys are not part of a mode group. It is an application bug to + * call this function for @ref LIBINPUT_EVENT_TABLET_PAD_KEY. + * * Mode switching is controlled by libinput and more than one mode may exist * on the tablet. This function returns the mode that this event's button, * ring or strip is logically in. If the button is a mode toggle button @@ -3102,6 +3160,9 @@ libinput_event_tablet_pad_get_mode(struct libinput_event_tablet_pad *event); * functionality, usually based on some visual feedback like LEDs on the * pad. * + * @note Pad keys are not part of a mode group. It is an application bug to + * call this function for @ref LIBINPUT_EVENT_TABLET_PAD_KEY. + * * The returned mode group is not refcounted and may become invalid after * the next call to libinput. Use libinput_tablet_pad_mode_group_ref() and * libinput_tablet_pad_mode_group_unref() to continue using the handle @@ -4041,7 +4102,7 @@ libinput_device_get_size(struct libinput_device *device, * @ingroup device * * Check if a @ref LIBINPUT_DEVICE_CAP_POINTER device has a button with the - * given code (see linux/input.h). + * given code (see linux/input-event-codes.h). * * @param device A current input device * @param code Button code to check for, e.g. BTN_LEFT @@ -4056,7 +4117,7 @@ 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). + * given code (see linux/input-event-codes.h). * * @param device A current input device * @param code Key code to check for, e.g. KEY_ESC @@ -4156,6 +4217,24 @@ libinput_device_tablet_pad_get_num_strips(struct libinput_device *device); /** * @ingroup device * + * Check if a @ref LIBINPUT_DEVICE_CAP_TABLET_PAD device has a key with the + * given code (see linux/input-event-codes.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. + * + * @since 1.15 + */ +int +libinput_device_tablet_pad_has_key(struct libinput_device *device, + uint32_t code); + +/** + * @ingroup device + * * Increase the refcount of the device group. A device group will be freed * whenever the refcount reaches 0. This may happen during * libinput_dispatch() if all devices of this group were removed from the @@ -5594,6 +5673,81 @@ libinput_device_config_scroll_get_button(struct libinput_device *device); uint32_t libinput_device_config_scroll_get_default_button(struct libinput_device *device); +enum libinput_config_scroll_button_lock_state { + LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_DISABLED, + LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_ENABLED, +}; + +/** + * @ingroup config + * + * Set the scroll button lock. If the state is + * @ref LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_DISABLED, the button must + * physically be held down for button scrolling to work. + * If the state is + * @ref LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_ENABLED, the button is considered + * logically down after the first press and release sequence, and logically + * up after the second press and release sequence. + * + * @param device The device to configure + * @param state The state to set the scroll button lock to + * + * @return A config status code. Disabling the scroll button lock on + * device that does not support button scrolling always succeeds. + * + * @see libinput_device_config_scroll_set_button + * @see libinput_device_config_scroll_get_button + * @see libinput_device_config_scroll_get_default_button + */ +enum libinput_config_status +libinput_device_config_scroll_set_button_lock(struct libinput_device *device, + enum libinput_config_scroll_button_lock_state state); + +/** + * @ingroup config + * + * Get the current scroll button lock state. + * + * If @ref LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN scroll method is not + * supported, or no button is set, this function returns @ref + * LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_DISABLED. + * + * @note The return value is independent of the currently selected + * scroll-method. For the scroll button lock to activate, a device must have + * the @ref LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN method enabled, and a + * non-zero button set as scroll button. + * + * @param device The device to configure + * @return The scroll button lock state + * + * @see libinput_device_config_scroll_set_button + * @see libinput_device_config_scroll_set_button_lock + * @see libinput_device_config_scroll_get_button_lock + * @see libinput_device_config_scroll_get_default_button_lock + */ +enum libinput_config_scroll_button_lock_state +libinput_device_config_scroll_get_button_lock(struct libinput_device *device); + +/** + * @ingroup config + * + * Get the default scroll button lock state. + * + * If @ref LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN scroll method is not + * supported, or no button is set, this function returns @ref + * LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_DISABLED. + * + * @param device The device to configure + * @return The default scroll button lock state + * + * @see libinput_device_config_scroll_set_button + * @see libinput_device_config_scroll_set_button_lock + * @see libinput_device_config_scroll_get_button_lock + * @see libinput_device_config_scroll_get_default_button_lock + */ +enum libinput_config_scroll_button_lock_state +libinput_device_config_scroll_get_default_button_lock(struct libinput_device *device); + /** * @ingroup config * diff --git a/src/libinput.sym b/src/libinput.sym index ef9d917..b45838e 100644 --- a/src/libinput.sym +++ b/src/libinput.sym @@ -305,3 +305,12 @@ LIBINPUT_1.14 { libinput_event_tablet_tool_size_major_has_changed; libinput_event_tablet_tool_size_minor_has_changed; } LIBINPUT_1.11; + +LIBINPUT_1.15 { + libinput_device_config_scroll_set_button_lock; + libinput_device_config_scroll_get_button_lock; + libinput_device_config_scroll_get_default_button_lock; + libinput_device_tablet_pad_has_key; + libinput_event_tablet_pad_get_key; + libinput_event_tablet_pad_get_key_state; +} LIBINPUT_1.14; diff --git a/src/path-seat.c b/src/path-seat.c index 334aa7c..99b089a 100644 --- a/src/path-seat.c +++ b/src/path-seat.c @@ -23,8 +23,6 @@ #include "config.h" -#include -#include #include #include #include diff --git a/src/quirks.c b/src/quirks.c index 7c0b75a..45d1f55 100644 --- a/src/quirks.c +++ b/src/quirks.c @@ -38,7 +38,6 @@ #include "libinput-versionsort.h" #include "libinput-util.h" -#include "libinput-private.h" #include "quirks.h" @@ -230,7 +229,7 @@ const char * quirk_get_name(enum quirk q) { switch(q) { - case QUIRK_MODEL_ALPS_TOUCHPAD: return "ModelALPSTouchpad"; + case QUIRK_MODEL_ALPS_SERIAL_TOUCHPAD: return "ModelALPSSerialTouchpad"; case QUIRK_MODEL_APPLE_TOUCHPAD: return "ModelAppleTouchpad"; case QUIRK_MODEL_APPLE_TOUCHPAD_ONEBUTTON: return "ModelAppleTouchpadOneButton"; case QUIRK_MODEL_BOUNCING_KEYS: return "ModelBouncingKeys"; @@ -245,6 +244,7 @@ quirk_get_name(enum quirk q) case QUIRK_MODEL_LENOVO_T450_TOUCHPAD: return "ModelLenovoT450Touchpad"; case QUIRK_MODEL_LENOVO_T480S_TOUCHPAD: return "ModelLenovoT480sTouchpad"; case QUIRK_MODEL_LENOVO_T490S_TOUCHPAD: return "ModelLenovoT490sTouchpad"; + case QUIRK_MODEL_LENOVO_X1GEN6_TOUCHPAD: return "ModelLenovoX1Gen6Touchpad"; case QUIRK_MODEL_LENOVO_X230: return "ModelLenovoX230"; case QUIRK_MODEL_SYNAPTICS_SERIAL_TOUCHPAD: return "ModelSynapticsSerialTouchpad"; case QUIRK_MODEL_SYSTEM76_BONOBO: return "ModelSystem76Bonobo"; @@ -255,7 +255,6 @@ quirk_get_name(enum quirk q) case QUIRK_MODEL_TOUCHPAD_VISIBLE_MARKER: return "ModelTouchpadVisibleMarker"; case QUIRK_MODEL_TRACKBALL: return "ModelTrackball"; case QUIRK_MODEL_WACOM_TOUCHPAD: return "ModelWacomTouchpad"; - case QUIRK_MODEL_WACOM_ISDV4_PEN: return "ModelWacomISDV4Pen"; case QUIRK_MODEL_DELL_CANVAS_TOTEM: return "ModelDellCanvasTotem"; case QUIRK_ATTR_SIZE_HINT: return "AttrSizeHint"; @@ -575,7 +574,7 @@ parse_model(struct quirks_context *ctx, const char *value) { bool b; - enum quirk q = QUIRK_MODEL_ALPS_TOUCHPAD; + enum quirk q = QUIRK_MODEL_ALPS_SERIAL_TOUCHPAD; assert(strneq(key, "Model", 5)); @@ -980,15 +979,7 @@ out: static int is_data_file(const struct dirent *dir) { - const char *suffix = ".quirks"; - const int slen = strlen(suffix); - int offset; - - offset = strlen(dir->d_name) - slen; - if (offset <= 0) - return 0; - - return strneq(&dir->d_name[offset], suffix, slen); + return strendswith(dir->d_name, ".quirks"); } static inline bool diff --git a/src/quirks.h b/src/quirks.h index 88159b5..ee85fe3 100644 --- a/src/quirks.h +++ b/src/quirks.h @@ -62,7 +62,7 @@ struct quirk_tuples { * Quirks known to libinput */ enum quirk { - QUIRK_MODEL_ALPS_TOUCHPAD = 100, + QUIRK_MODEL_ALPS_SERIAL_TOUCHPAD = 100, QUIRK_MODEL_APPLE_TOUCHPAD, QUIRK_MODEL_APPLE_TOUCHPAD_ONEBUTTON, QUIRK_MODEL_BOUNCING_KEYS, @@ -77,6 +77,7 @@ enum quirk { QUIRK_MODEL_LENOVO_T450_TOUCHPAD, QUIRK_MODEL_LENOVO_T480S_TOUCHPAD, QUIRK_MODEL_LENOVO_T490S_TOUCHPAD, + QUIRK_MODEL_LENOVO_X1GEN6_TOUCHPAD, QUIRK_MODEL_LENOVO_X230, QUIRK_MODEL_SYNAPTICS_SERIAL_TOUCHPAD, QUIRK_MODEL_SYSTEM76_BONOBO, @@ -87,7 +88,6 @@ enum quirk { QUIRK_MODEL_TOUCHPAD_VISIBLE_MARKER, QUIRK_MODEL_TRACKBALL, QUIRK_MODEL_WACOM_TOUCHPAD, - QUIRK_MODEL_WACOM_ISDV4_PEN, QUIRK_MODEL_DELL_CANVAS_TOTEM, _QUIRK_LAST_MODEL_QUIRK_, /* Guard: do not modify */ diff --git a/src/timer.c b/src/timer.c index 3deaf5e..9d23d79 100644 --- a/src/timer.c +++ b/src/timer.c @@ -25,7 +25,6 @@ #include #include -#include #include #include #include @@ -94,7 +93,7 @@ libinput_timer_set_flags(struct libinput_timer *timer, if (expire < now) { if ((flags & TIMER_FLAG_ALLOW_NEGATIVE) == 0) log_bug_client(timer->libinput, - "timer %s: offset negative (-%dms)\n", + "timer %s: scheduled expiry is in the past (-%dms), your system is too slow\n", timer->timer_name, us2ms(now - expire)); } else if ((expire - now) > ms2us(5000)) { diff --git a/src/udev-seat.c b/src/udev-seat.c index b548dfe..de78929 100644 --- a/src/udev-seat.c +++ b/src/udev-seat.c @@ -27,8 +27,6 @@ #include #include #include -#include -#include #include "evdev.h" #include "udev-seat.h" @@ -43,6 +41,38 @@ udev_seat_create(struct udev_input *input, static struct udev_seat * udev_seat_get_named(struct udev_input *input, const char *seat_name); + +static inline bool +filter_duplicates(struct udev_seat *udev_seat, + struct udev_device *udev_device) +{ + struct libinput_device *device; + const char *new_syspath = udev_device_get_syspath(udev_device); + bool ignore_device = false; + + if (!udev_seat) + return false; + + list_for_each(device, &udev_seat->base.devices_list, link) { + const char *syspath; + struct udev_device *ud; + + ud = libinput_device_get_udev_device(device); + if (!ud) + continue; + + syspath = udev_device_get_syspath(ud); + if (syspath && new_syspath && streq(syspath, new_syspath)) + ignore_device = true; + udev_device_unref(ud); + + if (ignore_device) + break; + } + + return ignore_device; +} + static int device_added(struct udev_device *udev_device, struct udev_input *input, @@ -74,6 +104,13 @@ device_added(struct udev_device *udev_device, seat = udev_seat_get_named(input, seat_name); + /* There is a race at startup: a device added between setting + * up the udev monitor and enumerating all current devices may show + * up in both lists. Filter those out. + */ + if (filter_duplicates(seat, udev_device)) + return 0; + if (seat) libinput_seat_ref(&seat->base); else { diff --git a/src/util-bits.h b/src/util-bits.h new file mode 100644 index 0000000..47c40f3 --- /dev/null +++ b/src/util-bits.h @@ -0,0 +1,101 @@ +/* + * Copyright © 2008-2011 Kristian Høgsberg + * Copyright © 2011 Intel Corporation + * Copyright © 2013-2015 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#pragma once + +#include "config.h" + +#include +#include +#include + +#define bit(x_) (1UL << (x_)) +#define NBITS(b) (b * 8) +#define LONG_BITS (sizeof(long) * 8) +#define NLONGS(x) (((x) + LONG_BITS - 1) / LONG_BITS) +#define NCHARS(x) ((size_t)(((x) + 7) / 8)) + + +/* This bitfield helper implementation is taken from from libevdev-util.h, + * except that it has been modified to work with arrays of unsigned chars + */ + +static inline bool +bit_is_set(const unsigned char *array, int bit) +{ + return !!(array[bit / 8] & (1 << (bit % 8))); +} + +static inline void +set_bit(unsigned char *array, int bit) +{ + array[bit / 8] |= (1 << (bit % 8)); +} + + static inline void +clear_bit(unsigned char *array, int bit) +{ + array[bit / 8] &= ~(1 << (bit % 8)); +} + +static inline bool +long_bit_is_set(const unsigned long *array, int bit) +{ + return !!(array[bit / LONG_BITS] & (1ULL << (bit % LONG_BITS))); +} + +static inline void +long_set_bit(unsigned long *array, int bit) +{ + array[bit / LONG_BITS] |= (1ULL << (bit % LONG_BITS)); +} + +static inline void +long_clear_bit(unsigned long *array, int bit) +{ + array[bit / LONG_BITS] &= ~(1ULL << (bit % LONG_BITS)); +} + +static inline void +long_set_bit_state(unsigned long *array, int bit, int state) +{ + if (state) + long_set_bit(array, bit); + else + long_clear_bit(array, bit); +} + +static inline bool +long_any_bit_set(unsigned long *array, size_t size) +{ + unsigned long i; + + assert(size > 0); + + for (i = 0; i < size; i++) + if (array[i] != 0) + return true; + return false; +} diff --git a/src/util-input-event.h b/src/util-input-event.h new file mode 100644 index 0000000..0c49bf7 --- /dev/null +++ b/src/util-input-event.h @@ -0,0 +1,69 @@ +/* + * Copyright © 2019 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#pragma once + +#include "config.h" + +#include "util-time.h" +#include + +static inline struct input_event +input_event_init(uint64_t time, + unsigned int type, + unsigned int code, + int value) +{ + struct input_event ev; + struct timeval tval = us2tv(time); + + ev.input_event_sec = tval.tv_sec; + ev.input_event_usec = tval.tv_usec; + ev.type = type; + ev.code = code; + ev.value = value; + + return ev; +} + +static inline uint64_t +input_event_time(const struct input_event *e) +{ + struct timeval tval; + + tval.tv_sec = e->input_event_sec; + tval.tv_usec = e->input_event_usec; + + return tv2us(&tval); +} + + +static inline void +input_event_set_time(struct input_event *e, + uint64_t time) +{ + struct timeval tval = us2tv(time); + + e->input_event_sec = tval.tv_sec; + e->input_event_usec = tval.tv_usec; +} diff --git a/src/util-list.c b/src/util-list.c new file mode 100644 index 0000000..45fed45 --- /dev/null +++ b/src/util-list.c @@ -0,0 +1,88 @@ +/* + * Copyright © 2008-2011 Kristian Høgsberg + * Copyright © 2011 Intel Corporation + * Copyright © 2013-2015 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include + +#include "util-list.h" + +void +list_init(struct list *list) +{ + list->prev = list; + list->next = list; +} + +void +list_insert(struct list *list, struct list *elm) +{ + assert((list->next != NULL && list->prev != NULL) || + !"list->next|prev is NULL, possibly missing list_init()"); + assert(((elm->next == NULL && elm->prev == NULL) || list_empty(elm)) || + !"elm->next|prev is not NULL, list node used twice?"); + + elm->prev = list; + elm->next = list->next; + list->next = elm; + elm->next->prev = elm; +} + +void +list_append(struct list *list, struct list *elm) +{ + assert((list->next != NULL && list->prev != NULL) || + !"list->next|prev is NULL, possibly missing list_init()"); + assert(((elm->next == NULL && elm->prev == NULL) || list_empty(elm)) || + !"elm->next|prev is not NULL, list node used twice?"); + + elm->next = list; + elm->prev = list->prev; + list->prev = elm; + elm->prev->next = elm; +} + +void +list_remove(struct list *elm) +{ + assert((elm->next != NULL && elm->prev != NULL) || + !"list->next|prev is NULL, possibly missing list_init()"); + + elm->prev->next = elm->next; + elm->next->prev = elm->prev; + elm->next = NULL; + elm->prev = NULL; +} + +bool +list_empty(const struct list *list) +{ + assert((list->next != NULL && list->prev != NULL) || + !"list->next|prev is NULL, possibly missing list_init()"); + + return list->next == list; +} diff --git a/src/util-list.h b/src/util-list.h new file mode 100644 index 0000000..292665a --- /dev/null +++ b/src/util-list.h @@ -0,0 +1,67 @@ +/* + * Copyright © 2008-2011 Kristian Høgsberg + * Copyright © 2011 Intel Corporation + * Copyright © 2013-2015 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#pragma once + +#include "config.h" + +#include +#include + +/* + * This list data structure is a verbatim copy from wayland-util.h from the + * Wayland project; except that wl_ prefix has been removed. + */ + +struct list { + struct list *prev; + struct list *next; +}; + +void list_init(struct list *list); +void list_insert(struct list *list, struct list *elm); +void list_append(struct list *list, struct list *elm); +void list_remove(struct list *elm); +bool list_empty(const struct list *list); + +#define container_of(ptr, type, member) \ + (__typeof__(type) *)((char *)(ptr) - \ + offsetof(__typeof__(type), member)) + +#define list_first_entry(head, pos, member) \ + container_of((head)->next, __typeof__(*pos), member) + +#define list_for_each(pos, head, member) \ + for (pos = 0, pos = list_first_entry(head, pos, member); \ + &pos->member != (head); \ + pos = list_first_entry(&pos->member, pos, member)) + +#define list_for_each_safe(pos, tmp, head, member) \ + for (pos = 0, tmp = 0, \ + pos = list_first_entry(head, pos, member), \ + tmp = list_first_entry(&pos->member, tmp, member); \ + &pos->member != (head); \ + pos = tmp, \ + tmp = list_first_entry(&pos->member, tmp, member)) diff --git a/src/util-macros.h b/src/util-macros.h new file mode 100644 index 0000000..785c25b --- /dev/null +++ b/src/util-macros.h @@ -0,0 +1,59 @@ +/* + * Copyright © 2008-2011 Kristian Høgsberg + * Copyright © 2011 Intel Corporation + * Copyright © 2013-2015 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#pragma once + +#include "config.h" + +#define ARRAY_LENGTH(a) (sizeof (a) / sizeof (a)[0]) +#define ARRAY_FOR_EACH(_arr, _elem) \ + for (size_t _i = 0; _i < ARRAY_LENGTH(_arr) && (_elem = &_arr[_i]); _i++) + +#define min(a, b) (((a) < (b)) ? (a) : (b)) +#define max(a, b) (((a) > (b)) ? (a) : (b)) + +#define ANSI_HIGHLIGHT "\x1B[0;1;39m" +#define ANSI_RED "\x1B[0;31m" +#define ANSI_GREEN "\x1B[0;32m" +#define ANSI_YELLOW "\x1B[0;33m" +#define ANSI_BLUE "\x1B[0;34m" +#define ANSI_MAGENTA "\x1B[0;35m" +#define ANSI_CYAN "\x1B[0;36m" +#define ANSI_BRIGHT_RED "\x1B[0;31;1m" +#define ANSI_BRIGHT_GREEN "\x1B[0;32;1m" +#define ANSI_BRIGHT_YELLOW "\x1B[0;33;1m" +#define ANSI_BRIGHT_BLUE "\x1B[0;34;1m" +#define ANSI_BRIGHT_MAGENTA "\x1B[0;35;1m" +#define ANSI_BRIGHT_CYAN "\x1B[0;36;1m" +#define ANSI_NORMAL "\x1B[0m" + + +#define ANSI_UP "\x1B[%dA" +#define ANSI_DOWN "\x1B[%dB" +#define ANSI_RIGHT "\x1B[%dC" +#define ANSI_LEFT "\x1B[%dD" + + +#define CASE_RETURN_STRING(a) case a: return #a diff --git a/src/util-matrix.h b/src/util-matrix.h new file mode 100644 index 0000000..e5a60cb --- /dev/null +++ b/src/util-matrix.h @@ -0,0 +1,162 @@ +/* + * Copyright © 2008 Kristian Høgsberg + * Copyright © 2013-2015 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + + +#pragma once + +#include "config.h" + +#include +#include +#include + +struct matrix { + float val[3][3]; /* [row][col] */ +}; + +static inline double +deg2rad(int degree) +{ + return M_PI * degree / 180.0; +} + +static inline void +matrix_init_identity(struct matrix *m) +{ + memset(m, 0, sizeof(*m)); + m->val[0][0] = 1; + m->val[1][1] = 1; + m->val[2][2] = 1; +} + +static inline void +matrix_from_farray6(struct matrix *m, const float values[6]) +{ + matrix_init_identity(m); + m->val[0][0] = values[0]; + m->val[0][1] = values[1]; + m->val[0][2] = values[2]; + m->val[1][0] = values[3]; + m->val[1][1] = values[4]; + m->val[1][2] = values[5]; +} + +static inline void +matrix_init_scale(struct matrix *m, float sx, float sy) +{ + matrix_init_identity(m); + m->val[0][0] = sx; + m->val[1][1] = sy; +} + +static inline void +matrix_init_translate(struct matrix *m, float x, float y) +{ + matrix_init_identity(m); + m->val[0][2] = x; + m->val[1][2] = y; +} + +static inline void +matrix_init_rotate(struct matrix *m, int degrees) +{ + double s, c; + + s = sin(deg2rad(degrees)); + c = cos(deg2rad(degrees)); + + matrix_init_identity(m); + m->val[0][0] = c; + m->val[0][1] = -s; + m->val[1][0] = s; + m->val[1][1] = c; +} + +static inline bool +matrix_is_identity(const struct matrix *m) +{ + return (m->val[0][0] == 1 && + m->val[0][1] == 0 && + m->val[0][2] == 0 && + m->val[1][0] == 0 && + m->val[1][1] == 1 && + m->val[1][2] == 0 && + m->val[2][0] == 0 && + m->val[2][1] == 0 && + m->val[2][2] == 1); +} + +static inline void +matrix_mult(struct matrix *dest, + const struct matrix *m1, + const struct matrix *m2) +{ + struct matrix m; /* allow for dest == m1 or dest == m2 */ + int row, col, i; + + for (row = 0; row < 3; row++) { + for (col = 0; col < 3; col++) { + double v = 0; + for (i = 0; i < 3; i++) { + v += m1->val[row][i] * m2->val[i][col]; + } + m.val[row][col] = v; + } + } + + memcpy(dest, &m, sizeof(m)); +} + +static inline void +matrix_mult_vec(const struct matrix *m, int *x, int *y) +{ + int tx, ty; + + tx = *x * m->val[0][0] + *y * m->val[0][1] + m->val[0][2]; + ty = *x * m->val[1][0] + *y * m->val[1][1] + m->val[1][2]; + + *x = tx; + *y = ty; +} + +static inline void +matrix_to_farray6(const struct matrix *m, float out[6]) +{ + out[0] = m->val[0][0]; + out[1] = m->val[0][1]; + out[2] = m->val[0][2]; + out[3] = m->val[1][0]; + out[4] = m->val[1][1]; + out[5] = m->val[1][2]; +} + +static inline void +matrix_to_relative(struct matrix *dest, const struct matrix *src) +{ + matrix_init_identity(dest); + dest->val[0][0] = src->val[0][0]; + dest->val[0][1] = src->val[0][1]; + dest->val[1][0] = src->val[1][0]; + dest->val[1][1] = src->val[1][1]; +} diff --git a/src/util-prop-parsers.c b/src/util-prop-parsers.c new file mode 100644 index 0000000..9e07632 --- /dev/null +++ b/src/util-prop-parsers.c @@ -0,0 +1,470 @@ +/* + * Copyright © 2013-2019 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "util-prop-parsers.h" + +#include +#include + +#include "util-macros.h" +#include "util-strings.h" + +/* Helper function to parse the mouse DPI tag from udev. + * The tag is of the form: + * MOUSE_DPI=400 *1000 2000 + * or + * MOUSE_DPI=400@125 *1000@125 2000@125 + * Where the * indicates the default value and @number indicates device poll + * rate. + * Numbers should be in ascending order, and if rates are present they should + * be present for all entries. + * + * When parsing the mouse DPI property, if we find an error we just return 0 + * since it's obviously invalid, the caller will treat that as an error and + * use a reasonable default instead. If the property contains multiple DPI + * settings but none flagged as default, we return the last because we're + * lazy and that's a silly way to set the property anyway. + * + * @param prop The value of the udev property (without the MOUSE_DPI=) + * @return The default dpi value on success, 0 on error + */ +int +parse_mouse_dpi_property(const char *prop) +{ + bool is_default = false; + int nread, dpi = 0, rate; + + if (!prop) + return 0; + + while (*prop != 0) { + if (*prop == ' ') { + prop++; + continue; + } + if (*prop == '*') { + prop++; + is_default = true; + if (!isdigit(prop[0])) + return 0; + } + + /* While we don't do anything with the rate right now we + * will validate that, if it's present, it is non-zero and + * positive + */ + rate = 1; + nread = 0; + sscanf(prop, "%d@%d%n", &dpi, &rate, &nread); + if (!nread) + sscanf(prop, "%d%n", &dpi, &nread); + if (!nread || dpi <= 0 || rate <= 0 || prop[nread] == '@') + return 0; + + if (is_default) + break; + prop += nread; + } + return dpi; +} + +/** + * Helper function to parse the MOUSE_WHEEL_CLICK_COUNT property from udev. + * Property is of the form: + * MOUSE_WHEEL_CLICK_COUNT= + * Where the number indicates the number of wheel clicks per 360 deg + * rotation. + * + * @param prop The value of the udev property (without the MOUSE_WHEEL_CLICK_COUNT=) + * @return The click count of the wheel (may be negative) or 0 on error. + */ +int +parse_mouse_wheel_click_count_property(const char *prop) +{ + int count = 0; + + if (!prop) + return 0; + + if (!safe_atoi(prop, &count) || abs(count) > 360) + return 0; + + return count; +} + +/** + * + * Helper function to parse the MOUSE_WHEEL_CLICK_ANGLE property from udev. + * Property is of the form: + * MOUSE_WHEEL_CLICK_ANGLE= + * Where the number indicates the degrees travelled for each click. + * + * @param prop The value of the udev property (without the MOUSE_WHEEL_CLICK_ANGLE=) + * @return The angle of the wheel (may be negative) or 0 on error. + */ +int +parse_mouse_wheel_click_angle_property(const char *prop) +{ + int angle = 0; + + if (!prop) + return 0; + + if (!safe_atoi(prop, &angle) || abs(angle) > 360) + return 0; + + return angle; +} + +/** + * Parses a simple dimension string in the form of "10x40". The two + * numbers must be positive integers in decimal notation. + * On success, the two numbers are stored in w and h. On failure, w and h + * are unmodified. + * + * @param prop The value of the property + * @param w Returns the first component of the dimension + * @param h Returns the second component of the dimension + * @return true on success, false otherwise + */ +bool +parse_dimension_property(const char *prop, size_t *w, size_t *h) +{ + int x, y; + + if (!prop) + return false; + + if (sscanf(prop, "%dx%d", &x, &y) != 2) + return false; + + if (x <= 0 || y <= 0) + return false; + + *w = (size_t)x; + *h = (size_t)y; + return true; +} + +/** + * Parses a set of 6 space-separated floats. + * + * @param prop The string value of the property + * @param calibration Returns the six components + * @return true on success, false otherwise + */ +bool +parse_calibration_property(const char *prop, float calibration_out[6]) +{ + int idx; + char **strv; + float calibration[6]; + + if (!prop) + return false; + + strv = strv_from_string(prop, " "); + if (!strv) + return false; + + for (idx = 0; idx < 6; idx++) { + double v; + if (strv[idx] == NULL || !safe_atod(strv[idx], &v)) { + strv_free(strv); + return false; + } + + calibration[idx] = v; + } + + strv_free(strv); + + memcpy(calibration_out, calibration, sizeof(calibration)); + + return true; +} + +bool +parse_switch_reliability_property(const char *prop, + enum switch_reliability *reliability) +{ + if (!prop) { + *reliability = RELIABILITY_UNKNOWN; + return true; + } + + if (streq(prop, "reliable")) + *reliability = RELIABILITY_RELIABLE; + else if (streq(prop, "write_open")) + *reliability = RELIABILITY_WRITE_OPEN; + else + return false; + + return true; +} + +/** + * Parses a string with the allowed values: "below" + * The value refers to the position of the touchpad (relative to the + * keyboard, i.e. your average laptop would be 'below') + * + * @param prop The value of the property + * @param layout The layout + * @return true on success, false otherwise + */ +bool +parse_tpkbcombo_layout_poperty(const char *prop, + enum tpkbcombo_layout *layout) +{ + if (!prop) + return false; + + if (streq(prop, "below")) { + *layout = TPKBCOMBO_LAYOUT_BELOW; + return true; + } + + return false; +} + +/** + * Parses a string of the format "a:b" where both a and b must be integer + * numbers and a > b. Also allowed is the special string vaule "none" which + * amounts to unsetting the property. + * + * @param prop The value of the property + * @param hi Set to the first digit or 0 in case of 'none' + * @param lo Set to the second digit or 0 in case of 'none' + * @return true on success, false otherwise + */ +bool +parse_range_property(const char *prop, int *hi, int *lo) +{ + int first, second; + + if (!prop) + return false; + + if (streq(prop, "none")) { + *hi = 0; + *lo = 0; + return true; + } + + if (sscanf(prop, "%d:%d", &first, &second) != 2) + return false; + + if (second >= first) + return false; + + *hi = first; + *lo = second; + + return true; +} + +static bool +parse_evcode_string(const char *s, int *type_out, int *code_out) +{ + int type, code; + + if (strneq(s, "EV_", 3)) { + type = libevdev_event_type_from_name(s); + if (type == -1) + return false; + + code = EVENT_CODE_UNDEFINED; + } else { + struct map { + const char *str; + int type; + } map[] = { + { "KEY_", EV_KEY }, + { "BTN_", EV_KEY }, + { "ABS_", EV_ABS }, + { "REL_", EV_REL }, + { "SW_", EV_SW }, + }; + struct map *m; + bool found = false; + + ARRAY_FOR_EACH(map, m) { + if (!strstartswith(s, m->str)) + continue; + + type = m->type; + code = libevdev_event_code_from_name(type, s); + if (code == -1) + return false; + + found = true; + break; + } + if (!found) + return false; + } + + *type_out = type; + *code_out = code; + + return true; +} + +/** + * Parses a string of the format "EV_ABS;KEY_A;BTN_TOOL_DOUBLETAP;ABS_X;" + * where each element must be a named event type OR a named event code OR a + * tuple in the form of EV_KEY:0x123, i.e. a named event type followed by a + * hex event code. + * + * events must point to an existing array of size nevents. + * nevents specifies the size of the array in events and returns the number + * of items, elements exceeding nevents are simply ignored, just make sure + * events is large enough for your use-case. + * + * The results are returned as input events with type and code set, all + * other fields undefined. Where only the event type is specified, the code + * is set to EVENT_CODE_UNDEFINED. + * + * On success, events contains nevents events. + */ +bool +parse_evcode_property(const char *prop, struct input_event *events, size_t *nevents) +{ + char **strv = NULL; + bool rc = false; + size_t ncodes = 0; + size_t idx; + /* A randomly chosen max so we avoid crazy quirks */ + struct input_event evs[32]; + + memset(evs, 0, sizeof evs); + + strv = strv_from_string(prop, ";"); + if (!strv) + goto out; + + for (idx = 0; strv[idx]; idx++) + ncodes++; + + if (ncodes == 0 || ncodes > ARRAY_LENGTH(evs)) + goto out; + + ncodes = min(*nevents, ncodes); + for (idx = 0; strv[idx]; idx++) { + char *s = strv[idx]; + + int type, code; + + if (strstr(s, ":") == NULL) { + if (!parse_evcode_string(s, &type, &code)) + goto out; + } else { + int consumed; + char stype[13] = {0}; /* EV_FF_STATUS + '\0' */ + + if (sscanf(s, "%12[A-Z_]:%x%n", stype, &code, &consumed) != 2 || + strlen(s) != (size_t)consumed || + (type = libevdev_event_type_from_name(stype)) == -1 || + code < 0 || code > libevdev_event_type_get_max(type)) + goto out; + } + + evs[idx].type = type; + evs[idx].code = code; + } + + memcpy(events, evs, ncodes * sizeof *events); + *nevents = ncodes; + rc = true; + +out: + strv_free(strv); + return rc; +} + +/** + * Parse the property value for the EVDEV_ABS_00 properties. Spec is + * EVDEV_ABS_00=min:max:res:fuzz:flat + * where any element may be empty and subsequent elements may not be + * present. So we have to parse + * EVDEV_ABS_00=min:max:res + * EVDEV_ABS_00=::res + * EVDEV_ABS_00=::res:fuzz: + * + * Returns a mask of the bits set and the absinfo struct with the values. + * The abs value for an unset bit is undefined. + */ +uint32_t +parse_evdev_abs_prop(const char *prop, struct input_absinfo *abs) +{ + char *str = strdup(prop); + char *current, *next; + uint32_t mask = 0; + int bit = ABS_MASK_MIN; + int *val; + int values[5]; + + /* basic sanity check: 5 digits for min/max, 3 for resolution, fuzz, + * flat and the colons. That's plenty, anything over is garbage */ + if (strlen(prop) > 24) + goto out; + + current = str; + val = values; + while (current && *current != '\0' && bit <= ABS_MASK_FLAT) { + if (*current != ':') { + int v; + next = index(current, ':'); + if (next) + *next = '\0'; + + if (!safe_atoi(current, &v)) { + mask = 0; + goto out; + } + *val = v; + mask |= bit; + current = next ? ++next : NULL; + } else { + current++; + } + bit <<= 1; + val++; + } + + if (mask & ABS_MASK_MIN) + abs->minimum = values[0]; + if (mask & ABS_MASK_MAX) + abs->maximum = values[1]; + if (mask & ABS_MASK_RES) + abs->resolution = values[2]; + if (mask & ABS_MASK_FUZZ) + abs->fuzz = values[3]; + if (mask & ABS_MASK_FLAT) + abs->flat = values[4]; + +out: + free(str); + + return mask; +} diff --git a/src/util-prop-parsers.h b/src/util-prop-parsers.h new file mode 100644 index 0000000..7ed136a --- /dev/null +++ b/src/util-prop-parsers.h @@ -0,0 +1,67 @@ +/* + * Copyright © 2013-2019 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#pragma once + +#include "config.h" + +#include +#include +#include +#include + +int parse_mouse_dpi_property(const char *prop); +int parse_mouse_wheel_click_angle_property(const char *prop); +int parse_mouse_wheel_click_count_property(const char *prop); +bool parse_dimension_property(const char *prop, size_t *width, size_t *height); +bool parse_calibration_property(const char *prop, float calibration[6]); +bool parse_range_property(const char *prop, int *hi, int *lo); +#define EVENT_CODE_UNDEFINED 0xffff +bool parse_evcode_property(const char *prop, struct input_event *events, size_t *nevents); + +enum tpkbcombo_layout { + TPKBCOMBO_LAYOUT_UNKNOWN, + TPKBCOMBO_LAYOUT_BELOW, +}; +bool parse_tpkbcombo_layout_poperty(const char *prop, + enum tpkbcombo_layout *layout); + +enum switch_reliability { + RELIABILITY_UNKNOWN, + RELIABILITY_RELIABLE, + RELIABILITY_WRITE_OPEN, +}; + +bool +parse_switch_reliability_property(const char *prop, + enum switch_reliability *reliability); + +enum { + ABS_MASK_MIN = 0x1, + ABS_MASK_MAX = 0x2, + ABS_MASK_RES = 0x4, + ABS_MASK_FUZZ = 0x8, + ABS_MASK_FLAT = 0x10, +}; + +uint32_t parse_evdev_abs_prop(const char *prop, struct input_absinfo *abs); diff --git a/src/util-ratelimit.c b/src/util-ratelimit.c new file mode 100644 index 0000000..a3943b1 --- /dev/null +++ b/src/util-ratelimit.c @@ -0,0 +1,79 @@ +/* + * Copyright © 2008-2011 Kristian Høgsberg + * Copyright © 2011 Intel Corporation + * Copyright © 2013-2015 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include + +#include "util-ratelimit.h" +#include "util-time.h" + +void +ratelimit_init(struct ratelimit *r, uint64_t ival_us, unsigned int burst) +{ + r->interval = ival_us; + r->begin = 0; + r->burst = burst; + r->num = 0; +} + +/* + * Perform rate-limit test. Returns RATELIMIT_PASS if the rate-limited action + * is still allowed, RATELIMIT_THRESHOLD if the limit has been reached with + * this call, and RATELIMIT_EXCEEDED if you're beyond the threshold. + * It's safe to treat the return-value as boolean, if you're not interested in + * the exact state. It evaluates to "true" if the threshold hasn't been + * exceeded, yet. + * + * The ratelimit object must be initialized via ratelimit_init(). + * + * Modelled after Linux' lib/ratelimit.c by Dave Young + * , which is licensed GPLv2. + */ +enum ratelimit_state +ratelimit_test(struct ratelimit *r) +{ + struct timespec ts; + uint64_t utime; + + if (r->interval <= 0 || r->burst <= 0) + return RATELIMIT_PASS; + + clock_gettime(CLOCK_MONOTONIC, &ts); + utime = s2us(ts.tv_sec) + ns2us(ts.tv_nsec); + + if (r->begin <= 0 || r->begin + r->interval < utime) { + /* reset counter */ + r->begin = utime; + r->num = 1; + return RATELIMIT_PASS; + } else if (r->num < r->burst) { + /* continue burst */ + return (++r->num == r->burst) ? RATELIMIT_THRESHOLD + : RATELIMIT_PASS; + } + + return RATELIMIT_EXCEEDED; +} diff --git a/src/util-ratelimit.h b/src/util-ratelimit.h new file mode 100644 index 0000000..a717ae9 --- /dev/null +++ b/src/util-ratelimit.h @@ -0,0 +1,45 @@ +/* + * Copyright © 2008 Kristian Høgsberg + * Copyright © 2013-2015 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#pragma once + +#include "config.h" + +#include + +enum ratelimit_state { + RATELIMIT_EXCEEDED, + RATELIMIT_THRESHOLD, + RATELIMIT_PASS, +}; + +struct ratelimit { + uint64_t interval; + uint64_t begin; + unsigned int burst; + unsigned int num; +}; + +void ratelimit_init(struct ratelimit *r, uint64_t ival_ms, unsigned int burst); +enum ratelimit_state ratelimit_test(struct ratelimit *r); diff --git a/src/util-strings.c b/src/util-strings.c new file mode 100644 index 0000000..4fad175 --- /dev/null +++ b/src/util-strings.c @@ -0,0 +1,157 @@ +/* + * Copyright © 2008 Kristian Høgsberg + * Copyright © 2013-2015 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include "util-strings.h" + +/** + * Return the next word in a string pointed to by state before the first + * separator character. Call repeatedly to tokenize a whole string. + * + * @param state Current state + * @param len String length of the word returned + * @param separators List of separator characters + * + * @return The first word in *state, NOT null-terminated + */ +static const char * +next_word(const char **state, size_t *len, const char *separators) +{ + const char *next = *state; + size_t l; + + if (!*next) + return NULL; + + next += strspn(next, separators); + if (!*next) { + *state = next; + return NULL; + } + + l = strcspn(next, separators); + *state = next + l; + *len = l; + + return next; +} + +/** + * Return a null-terminated string array with the tokens in the input + * string, e.g. "one two\tthree" with a separator list of " \t" will return + * an array [ "one", "two", "three", NULL ]. + * + * Use strv_free() to free the array. + * + * @param in Input string + * @param separators List of separator characters + * + * @return A null-terminated string array or NULL on errors + */ +char ** +strv_from_string(const char *in, const char *separators) +{ + const char *s, *word; + char **strv = NULL; + int nelems = 0, idx; + size_t l; + + assert(in != NULL); + + s = in; + while (next_word(&s, &l, separators) != NULL) + nelems++; + + if (nelems == 0) + return NULL; + + nelems++; /* NULL-terminated */ + strv = zalloc(nelems * sizeof *strv); + + idx = 0; + + s = in; + while ((word = next_word(&s, &l, separators)) != NULL) { + char *copy = strndup(word, l); + if (!copy) { + strv_free(strv); + return NULL; + } + + strv[idx++] = copy; + } + + return strv; +} + +/** + * Return a newly allocated string with all elements joined by the + * joiner, same as Python's string.join() basically. + * A strv of ["one", "two", "three", NULL] with a joiner of ", " results + * in "one, two, three". + * + * An empty strv ([NULL]) returns NULL, same for passing NULL as either + * argument. + * + * @param strv Input string arrray + * @param joiner Joiner between the elements in the final string + * + * @return A null-terminated string joining all elements + */ +char * +strv_join(char **strv, const char *joiner) +{ + char **s; + char *str; + size_t slen = 0; + size_t count = 0; + + if (!strv || !joiner) + return NULL; + + if (strv[0] == NULL) + return NULL; + + for (s = strv, count = 0; *s; s++, count++) { + slen += strlen(*s); + } + + assert(slen < 1000); + assert(strlen(joiner) < 1000); + assert(count > 0); + assert(count < 100); + + slen += (count - 1) * strlen(joiner); + + str = zalloc(slen + 1); /* trailing \0 */ + for (s = strv; *s; s++) { + strcat(str, *s); + --count; + if (count > 0) + strcat(str, joiner); + } + + return str; +} diff --git a/src/util-strings.h b/src/util-strings.h new file mode 100644 index 0000000..2c31ff8 --- /dev/null +++ b/src/util-strings.h @@ -0,0 +1,366 @@ +/* + * Copyright © 2008 Kristian Høgsberg + * Copyright © 2013-2015 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#pragma once + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_LOCALE_H +#include +#endif +#ifdef HAVE_XLOCALE_H +#include +#endif + +#define streq(s1, s2) (strcmp((s1), (s2)) == 0) +#define strneq(s1, s2, n) (strncmp((s1), (s2), (n)) == 0) + +static inline void * +zalloc(size_t size) +{ + void *p; + + /* We never need to alloc anything more than 1,5 MB so we can assume + * if we ever get above that something's going wrong */ + if (size > 1536 * 1024) + assert(!"bug: internal malloc size limit exceeded"); + + p = calloc(1, size); + if (!p) + abort(); + + return p; +} + +/** + * strdup guaranteed to succeed. If the input string is NULL, the output + * string is NULL. If the input string is a string pointer, we strdup or + * abort on failure. + */ +static inline char* +safe_strdup(const char *str) +{ + char *s; + + if (!str) + return NULL; + + s = strdup(str); + if (!s) + abort(); + return s; +} + +/** + * Simple wrapper for asprintf that ensures the passed in-pointer is set + * to NULL upon error. + * The standard asprintf() call does not guarantee the passed in pointer + * will be NULL'ed upon failure, whereas this wrapper does. + * + * @param strp pointer to set to newly allocated string. + * This pointer should be passed to free() to release when done. + * @param fmt the format string to use for printing. + * @return The number of bytes printed (excluding the null byte terminator) + * upon success or -1 upon failure. In the case of failure the pointer is set + * to NULL. + */ +__attribute__ ((format (printf, 2, 3))) +static inline int +xasprintf(char **strp, const char *fmt, ...) +{ + int rc = 0; + va_list args; + + va_start(args, fmt); + rc = vasprintf(strp, fmt, args); + va_end(args); + if ((rc == -1) && strp) + *strp = NULL; + + return rc; +} + +static inline bool +safe_atoi_base(const char *str, int *val, int base) +{ + char *endptr; + long v; + + assert(base == 10 || base == 16 || base == 8); + + errno = 0; + v = strtol(str, &endptr, base); + if (errno > 0) + return false; + if (str == endptr) + return false; + if (*str != '\0' && *endptr != '\0') + return false; + + if (v > INT_MAX || v < INT_MIN) + return false; + + *val = v; + return true; +} + +static inline bool +safe_atoi(const char *str, int *val) +{ + return safe_atoi_base(str, val, 10); +} + +static inline bool +safe_atou_base(const char *str, unsigned int *val, int base) +{ + char *endptr; + unsigned long v; + + assert(base == 10 || base == 16 || base == 8); + + errno = 0; + v = strtoul(str, &endptr, base); + if (errno > 0) + return false; + if (str == endptr) + return false; + if (*str != '\0' && *endptr != '\0') + return false; + + if ((long)v < 0) + return false; + + *val = v; + return true; +} + +static inline bool +safe_atou(const char *str, unsigned int *val) +{ + return safe_atou_base(str, val, 10); +} + +static inline bool +safe_atod(const char *str, double *val) +{ + char *endptr; + double v; +#ifdef HAVE_LOCALE_H + locale_t c_locale; +#endif + size_t slen = strlen(str); + + /* We don't have a use-case where we want to accept hex for a double + * or any of the other values strtod can parse */ + for (size_t i = 0; i < slen; i++) { + char c = str[i]; + + if (isdigit(c)) + continue; + switch(c) { + case '+': + case '-': + case '.': + break; + default: + return false; + } + } + +#ifdef HAVE_LOCALE_H + /* 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 false; + + errno = 0; + v = strtod_l(str, &endptr, c_locale); + freelocale(c_locale); +#else + /* No locale support in provided libc, assume it already uses '.' */ + errno = 0; + v = strtod(str, &endptr); +#endif + if (errno > 0) + return false; + if (str == endptr) + return false; + if (*str != '\0' && *endptr != '\0') + return false; + if (v != 0.0 && !isnormal(v)) + return false; + + *val = v; + return true; +} + +char **strv_from_string(const char *string, const char *separator); +char *strv_join(char **strv, const char *separator); + +static inline void +strv_free(char **strv) { + char **s = strv; + + if (!strv) + return; + + while (*s != NULL) { + free(*s); + *s = (char*)0x1; /* detect use-after-free */ + s++; + } + + free (strv); +} + +struct key_value_str{ + char *key; + char *value; +}; + +struct key_value_double { + double key; + double value; +}; + +static inline ssize_t +kv_double_from_string(const char *string, + const char *pair_separator, + const char *kv_separator, + struct key_value_double **result_out) + +{ + char **pairs; + char **pair; + struct key_value_double *result = NULL; + ssize_t npairs = 0; + unsigned int idx = 0; + + if (!pair_separator || pair_separator[0] == '\0' || + !kv_separator || kv_separator[0] == '\0') + return -1; + + pairs = strv_from_string(string, pair_separator); + if (!pairs) + return -1; + + for (pair = pairs; *pair; pair++) + npairs++; + + if (npairs == 0) + goto error; + + result = zalloc(npairs * sizeof *result); + + for (pair = pairs; *pair; pair++) { + char **kv = strv_from_string(*pair, kv_separator); + double k, v; + + if (!kv || !kv[0] || !kv[1] || kv[2] || + !safe_atod(kv[0], &k) || + !safe_atod(kv[1], &v)) { + strv_free(kv); + goto error; + } + + result[idx].key = k; + result[idx].value = v; + idx++; + + strv_free(kv); + } + + strv_free(pairs); + + *result_out = result; + + return npairs; + +error: + strv_free(pairs); + free(result); + return -1; +} + +/** + * Strip any of the characters in what from the beginning and end of the + * input string. + * + * @return a newly allocated string with none of "what" at the beginning or + * end of string + */ +static inline char * +strstrip(const char *input, const char *what) +{ + char *str, *last; + + str = safe_strdup(&input[strspn(input, what)]); + + last = str; + + for (char *c = str; *c != '\0'; c++) { + if (!strchr(what, *c)) + last = c + 1; + } + + *last = '\0'; + + return str; +} + +/** + * Return true if str ends in suffix, false otherwise. If the suffix is the + * empty string, strendswith() always returns false. + */ +static inline bool +strendswith(const char *str, const char *suffix) +{ + size_t slen = strlen(str); + size_t suffixlen = strlen(suffix); + size_t offset; + + if (slen == 0 || suffixlen == 0 || suffixlen > slen) + return false; + + offset = slen - suffixlen; + return strneq(&str[offset], suffix, suffixlen); +} + +static inline bool +strstartswith(const char *str, const char *prefix) +{ + size_t prefixlen = strlen(prefix); + + return prefixlen > 0 ? strneq(str, prefix, strlen(prefix)) : false; +} diff --git a/src/util-time.h b/src/util-time.h new file mode 100644 index 0000000..c28283b --- /dev/null +++ b/src/util-time.h @@ -0,0 +1,126 @@ +/* + * Copyright © 2013-2019 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#pragma once + +#include "config.h" + +#include +#include +#include +#include +#include + +#include "util-macros.h" + +static inline void +msleep(unsigned int ms) +{ + usleep(ms * 1000); +} + +static inline uint64_t +us(uint64_t us) +{ + return us; +} + +static inline uint64_t +ns2us(uint64_t ns) +{ + return us(ns / 1000); +} + +static inline uint64_t +ms2us(uint64_t ms) +{ + return us(ms * 1000); +} + +static inline uint64_t +s2us(uint64_t s) +{ + return ms2us(s * 1000); +} + +static inline uint32_t +us2ms(uint64_t us) +{ + return (uint32_t)(us / 1000); +} + +static inline uint64_t +tv2us(const struct timeval *tv) +{ + return s2us(tv->tv_sec) + tv->tv_usec; +} + +static inline struct timeval +us2tv(uint64_t time) +{ + struct timeval tv; + + tv.tv_sec = time / ms2us(1000); + tv.tv_usec = time % ms2us(1000); + + return tv; +} + +struct human_time { + unsigned int value; + const char *unit; +}; + +/** + * Converts a time delta in µs to a human-readable time like "2h" or "4d" + */ +static inline struct human_time +to_human_time(uint64_t us) +{ + struct human_time t; + struct c { + const char *unit; + unsigned int change_from_previous; + uint64_t limit; + } conversion[] = { + {"us", 1, 5000}, + {"ms", 1000, 5000}, + {"s", 1000, 120}, + {"min", 60, 120}, + {"h", 60, 48}, + {"d", 24, ~0}, + }; + struct c *c; + uint64_t value = us; + + ARRAY_FOR_EACH(conversion, c) { + value = value/c->change_from_previous; + if (value < c->limit) { + t.unit = c->unit; + t.value = value; + return t; + } + } + + assert(!"We should never get here"); +} diff --git a/test/50-litest.conf b/test/50-litest.conf index 76579d7..8eeb315 100644 --- a/test/50-litest.conf +++ b/test/50-litest.conf @@ -1,6 +1,6 @@ # Ignore devices created by libinput's test suite (litest) Section "InputClass" - Identifier "libinput test suite blacklist" + Identifier "Ignore libinput test suite devices" MatchProduct "litest" Option "Ignore" "on" EndSection diff --git a/test/libinput-test-suite.man b/test/libinput-test-suite.man index 906dfe6..112aae0 100644 --- a/test/libinput-test-suite.man +++ b/test/libinput-test-suite.man @@ -76,7 +76,7 @@ devices created by this test suite: .RS 4 .nf .B "Section ""InputClass"" -.B " Identifier ""libinput test suite blacklist"" +.B " Identifier ""Ignore libinput test suite devices"" .B " MatchProduct ""litest"" .B " Option ""Ignore"" ""on"" .B "EndSection" diff --git a/test/litest-device-absinfo-override.c b/test/litest-device-absinfo-override.c new file mode 100644 index 0000000..ce8586e --- /dev/null +++ b/test/litest-device-absinfo-override.c @@ -0,0 +1,77 @@ +/* + * Copyright © 2020 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include "litest.h" +#include "litest-int.h" + +static struct input_id input_id = { + .bustype = 0x11, + .vendor = 0x1234, + .product = 0x4567, +}; + +static int events[] = { + EV_KEY, BTN_LEFT, + EV_KEY, BTN_RIGHT, + EV_KEY, BTN_MIDDLE, + EV_KEY, BTN_TOOL_FINGER, + EV_KEY, BTN_TOUCH, + EV_KEY, BTN_TOOL_DOUBLETAP, + EV_KEY, BTN_TOOL_TRIPLETAP, + EV_KEY, BTN_TOOL_QUADTAP, + INPUT_PROP_MAX, INPUT_PROP_POINTER, + -1 , -1, +}; + +static struct input_absinfo absinfo[] = { + { ABS_X, 0, 2000, 0, 0, 0 }, + { ABS_Y, 0, 1400, 0, 0, 0 }, + { ABS_PRESSURE, 0, 127, 0, 0, 0 }, + { ABS_MT_SLOT, 0, 1, 0, 0, 0 }, + { ABS_MT_POSITION_X, 0, 2000, 0, 0, 0 }, + { ABS_MT_POSITION_Y, 0, 1400, 0, 0, 0 }, + { ABS_MT_TRACKING_ID, 0, 65535, 0, 0, 0 }, + { .value = -1 } +}; + +/* This device only exists to verify that the EVDEV_ABS override bits work + * correctly */ +TEST_DEVICE("absinfo-override", + .type = LITEST_ABSINFO_OVERRIDE, + .features = LITEST_IGNORED, + .interface = NULL, + + .name = "absinfo override", + .id = &input_id, + .absinfo = absinfo, + .events = events, + .udev_properties = { + { "EVDEV_ABS_00", "1:1000:100:10" }, + { "EVDEV_ABS_01", "2:2000:200:20" }, + { "EVDEV_ABS_35", "3:3000:300:30" }, + { "EVDEV_ABS_36", "4:4000:400:40" }, + { NULL }, + }, +) diff --git a/test/litest-device-aiptek-tablet.c b/test/litest-device-aiptek-tablet.c index e417aa4..f2bc43e 100644 --- a/test/litest-device-aiptek-tablet.c +++ b/test/litest-device-aiptek-tablet.c @@ -27,6 +27,7 @@ #include "litest-int.h" static struct input_event proximity_in[] = { + /* Note: this device does not send BTN_TOOL_PEN */ { .type = EV_ABS, .code = ABS_X, .value = LITEST_AUTO_ASSIGN }, { .type = EV_ABS, .code = ABS_Y, .value = LITEST_AUTO_ASSIGN }, /* Note: this device does not send tilt, despite claiming it has it */ @@ -140,7 +141,7 @@ static int events[] = { TEST_DEVICE("aiptek-tablet", .type = LITEST_AIPTEK, - .features = LITEST_TABLET | LITEST_HOVER, + .features = LITEST_TABLET | LITEST_HOVER | LITEST_FORCED_PROXOUT, .interface = &interface, .name = "Aiptek", diff --git a/test/litest-device-alps-3fg.c b/test/litest-device-alps-3fg.c new file mode 100644 index 0000000..16a11ee --- /dev/null +++ b/test/litest-device-alps-3fg.c @@ -0,0 +1,179 @@ +/* + * Copyright © 2020 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include + +#include "libinput-util.h" + +#include "litest.h" +#include "litest-int.h" + +struct alps { + unsigned int first, second; + unsigned int active_touches; +}; + +static bool +alps_create(struct litest_device *d) +{ + d->private = zalloc(sizeof(struct alps)); + return true; /* we want litest to create our device */ +} + +static bool +touch_down(struct litest_device *d, unsigned int slot, double x, double y) +{ + struct alps *alps = d->private; + + alps->active_touches++; + + if (alps->active_touches == 1) + alps->first = slot; + if (alps->active_touches == 2) + alps->second = slot; + + /* This device announces 4 slots but only does two slots. So + * anything over 2 slots we just drop for events, + * litest takes care of BTN_TOOL_* for us. */ + if (alps->active_touches > 2) { + /* Need to send SYN_REPORT to flush litest's BTN_TOOL_* updates */ + litest_event(d, EV_SYN, SYN_REPORT, 0); + return true; + } + + return false; +} + +static bool +touch_move(struct litest_device *d, unsigned int slot, double x, double y) +{ + struct alps *alps = d->private; + + if (alps->active_touches > 2 && + slot != alps->first && + slot != alps->second) + return true; + + return false; +} + +static bool +touch_up(struct litest_device *d, unsigned int slot) +{ + struct alps *alps = d->private; + + assert(alps->active_touches >= 1); + alps->active_touches--; + + /* Need to send SYN_REPORT to flush litest's BTN_TOOL_* updates */ + if (alps->active_touches > 2 && + slot != alps->first && + slot != alps->second) { + litest_event(d, EV_SYN, SYN_REPORT, 0); + return true; + } + + if (slot == alps->first) + alps->first = UINT_MAX; + if (slot == alps->second) + alps->second = UINT_MAX; + + return false; +} + +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 }, + { .type = EV_ABS, .code = ABS_MT_POSITION_Y, .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_MT_POSITION_X, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_MT_POSITION_Y, .value = LITEST_AUTO_ASSIGN }, + { .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_down = touch_down, + .touch_move = touch_move, + .touch_up = touch_up, +}; + +static struct input_id input_id = { + .bustype = 0x11, + .vendor = 0x2, + .product = 0x8, + .version = 0x700, +}; + +static int events[] = { + EV_KEY, BTN_LEFT, + EV_KEY, BTN_RIGHT, + EV_KEY, BTN_MIDDLE, + 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, + -1, -1, +}; + +/* Note: we use the user-supplied resolution here, see #408 */ +static struct input_absinfo absinfo[] = { + { ABS_X, 0, 4095, 0, 0, 37 }, + { ABS_Y, 0, 2047, 0, 0, 26 }, + { ABS_PRESSURE, 0, 127, 0, 0, 0 }, + { ABS_MT_SLOT, 0, 3, 0, 0, 0 }, + { ABS_MT_POSITION_X, 0, 4095, 0, 0, 37 }, + { ABS_MT_POSITION_Y, 0, 2047, 0, 0, 26 }, + { ABS_MT_TRACKING_ID, 0, 65535, 0, 0, 0 }, + { .value = -1 } +}; + +TEST_DEVICE("alps-3fg", + .type = LITEST_ALPS_3FG, + .features = LITEST_TOUCHPAD | LITEST_BUTTON, + .interface = &interface, + + .name = "AlpsPS/2 ALPS GlidePoint", + .id = &input_id, + .events = events, + .absinfo = absinfo, + .create = alps_create, +) diff --git a/test/litest-device-elan-tablet.c b/test/litest-device-elan-tablet.c new file mode 100644 index 0000000..7b8e702 --- /dev/null +++ b/test/litest-device-elan-tablet.c @@ -0,0 +1,103 @@ +/* + * Copyright © 2020 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include "litest.h" +#include "litest-int.h" + +static struct input_event proximity_in[] = { + { .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_KEY, .code = BTN_TOOL_PEN, .value = 1 }, + { .type = EV_SYN, .code = SYN_REPORT, .value = 0 }, + { .type = -1, .code = -1 }, +}; + +static struct input_event proximity_out[] = { + { .type = EV_KEY, .code = BTN_TOOL_PEN, .value = 0 }, + { .type = EV_SYN, .code = SYN_REPORT, .value = 0 }, + { .type = -1, .code = -1 }, +}; + +static struct input_event motion[] = { + { .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_SYN, .code = SYN_REPORT, .value = 0 }, + { .type = -1, .code = -1 }, +}; + +static int +get_axis_default(struct litest_device *d, unsigned int evcode, int32_t *value) +{ + switch (evcode) { + case ABS_PRESSURE: + *value = 100; + return 0; + } + return 1; +} + +static struct litest_device_interface interface = { + .tablet_proximity_in_events = proximity_in, + .tablet_proximity_out_events = proximity_out, + .tablet_motion_events = motion, + + .get_axis_default = get_axis_default, +}; + +static struct input_absinfo absinfo[] = { + { ABS_X, 0, 18176, 0, 0, 62 }, + { ABS_Y, 0, 10240, 0, 0, 62 }, + { ABS_PRESSURE, 0, 4096, 0, 0, 0 }, + { .value = -1 }, +}; + +static struct input_id input_id = { + .bustype = 0x18, + .vendor = 0x4f3, + .product = 0x23b9, + .version = 0x100, +}; + +static int events[] = { + EV_KEY, BTN_TOOL_PEN, + EV_KEY, BTN_TOOL_RUBBER, + EV_KEY, BTN_TOUCH, + EV_KEY, BTN_STYLUS, + EV_MSC, MSC_SCAN, + -1, -1, +}; + +TEST_DEVICE("elan-tablet", + .type = LITEST_ELAN_TABLET, + .features = LITEST_TABLET, + .interface = &interface, + + .name = "ELAN2514:00 04F3:23B9", + .id = &input_id, + .events = events, + .absinfo = absinfo, +) diff --git a/test/litest-device-huion-pentablet.c b/test/litest-device-huion-pentablet.c index fbf1ae8..976d670 100644 --- a/test/litest-device-huion-pentablet.c +++ b/test/litest-device-huion-pentablet.c @@ -90,7 +90,7 @@ static int events[] = { TEST_DEVICE("huion-tablet", .type = LITEST_HUION_TABLET, - .features = LITEST_TABLET | LITEST_HOVER, + .features = LITEST_TABLET | LITEST_HOVER | LITEST_FORCED_PROXOUT, .interface = &interface, .name = "HUION PenTablet Pen", diff --git a/test/litest-device-logitech-media-keyboard-elite.c b/test/litest-device-logitech-media-keyboard-elite.c new file mode 100644 index 0000000..cac921b --- /dev/null +++ b/test/litest-device-logitech-media-keyboard-elite.c @@ -0,0 +1,92 @@ +/* + * Copyright © 2020 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include + +#include "libinput-util.h" + +#include "litest.h" +#include "litest-int.h" + +/* Description taken from + * https://gitlab.freedesktop.org/libinput/libinput/-/issues/514 + */ + +static struct input_id input_id = { + .bustype = 0x3, + .vendor = 0x46d, + .product = 0x30f, +}; + +static int events[] = { + EV_KEY, KEY_MUTE, + EV_KEY, KEY_VOLUMEDOWN, + EV_KEY, KEY_VOLUMEUP, + EV_KEY, KEY_UNDO, + EV_KEY, KEY_HELP, + EV_KEY, KEY_CALC, + EV_KEY, KEY_MAIL, + EV_KEY, KEY_BOOKMARKS, + EV_KEY, KEY_BACK, + EV_KEY, KEY_FORWARD, + EV_KEY, KEY_NEXTSONG, + EV_KEY, KEY_PLAYPAUSE, + EV_KEY, KEY_PREVIOUSSONG, + EV_KEY, KEY_STOPCD, + EV_KEY, KEY_REWIND, + EV_KEY, KEY_CONFIG, + EV_KEY, KEY_HOMEPAGE, + EV_KEY, KEY_REDO, + EV_KEY, KEY_FASTFORWARD, + EV_KEY, KEY_PRINT, + EV_KEY, KEY_SEARCH, + EV_KEY, KEY_SAVE, + EV_KEY, 319, + EV_KEY, BTN_TOOL_QUINTTAP, + EV_KEY, BTN_STYLUS3, + EV_KEY, BTN_TOUCH, + EV_KEY, BTN_STYLUS, + EV_KEY, KEY_ZOOMIN, + EV_KEY, KEY_ZOOMOUT, + EV_KEY, KEY_ZOOMRESET, + EV_KEY, KEY_WORDPROCESSOR, + EV_KEY, KEY_SPREADSHEET, + EV_KEY, KEY_PRESENTATION, + EV_KEY, KEY_MESSENGER, + + EV_MSC, MSC_SCAN, + -1, -1, +}; + +TEST_DEVICE("logitech-media-keyboard-elite", + .type = LITEST_KEYBOARD_LOGITECH_MEDIA_KEYBOARD_ELITE, + .features = LITEST_KEYS, + .interface = NULL, + + .name = "Logitech Logitech USB Keyboard Consumer Control", + .id = &input_id, + .events = events, + .absinfo = NULL, +) diff --git a/test/litest-device-protocol-a-touch-screen.c b/test/litest-device-protocol-a-touch-screen.c index 258955c..b27b04c 100644 --- a/test/litest-device-protocol-a-touch-screen.c +++ b/test/litest-device-protocol-a-touch-screen.c @@ -49,7 +49,7 @@ protocolA_create(struct litest_device *d) return true; /* we want litest to create our device */ } -static void +static bool protocolA_down(struct litest_device *d, unsigned int slot, double x, double y) { struct protocolA_device *dev = d->private; @@ -76,7 +76,7 @@ protocolA_down(struct litest_device *d, unsigned int slot, double x, double y) if (first) { litest_event(d, EV_ABS, ABS_X, s->x); - litest_event(d, EV_ABS, ABS_X, s->y); + litest_event(d, EV_ABS, ABS_Y, s->y); first = false; } @@ -90,9 +90,11 @@ protocolA_down(struct litest_device *d, unsigned int slot, double x, double y) litest_event(d, EV_KEY, BTN_TOUCH, 1); litest_event(d, EV_SYN, SYN_REPORT, 0); } + + return true; /* we handled the event */ } -static void +static bool protocolA_move(struct litest_device *d, unsigned int slot, double x, double y) { struct protocolA_device *dev = d->private; @@ -128,9 +130,11 @@ protocolA_move(struct litest_device *d, unsigned int slot, double x, double y) if (!first) litest_event(d, EV_SYN, SYN_REPORT, 0); + + return true; /* we handled the event */ } -static void +static bool protocolA_up(struct litest_device *d, unsigned int slot) { struct protocolA_device *dev = d->private; @@ -166,6 +170,8 @@ protocolA_up(struct litest_device *d, unsigned int slot) if (first) litest_event(d, EV_KEY, BTN_TOUCH, 0); litest_event(d, EV_SYN, SYN_REPORT, 0); + + return true; /* we handled the event */ } static struct litest_device_interface interface = { diff --git a/test/litest-device-qemu-usb-tablet.c b/test/litest-device-qemu-usb-tablet.c index bfcbc73..f8ed1b9 100644 --- a/test/litest-device-qemu-usb-tablet.c +++ b/test/litest-device-qemu-usb-tablet.c @@ -27,30 +27,37 @@ #include "litest-int.h" #include -static void touch_down(struct litest_device *d, unsigned int slot, - double x, double y) +static bool +touch_down(struct litest_device *d, unsigned int slot, double x, double y) { assert(slot == 0); litest_event(d, EV_ABS, ABS_X, litest_scale(d, ABS_X, x)); litest_event(d, EV_ABS, ABS_Y, litest_scale(d, ABS_Y, y)); litest_event(d, EV_SYN, SYN_REPORT, 0); + + return true; /* we handled the event */ } -static void touch_move(struct litest_device *d, unsigned int slot, - double x, double y) +static bool +touch_move(struct litest_device *d, unsigned int slot, double x, double y) { assert(slot == 0); litest_event(d, EV_ABS, ABS_X, litest_scale(d, ABS_X, x)); litest_event(d, EV_ABS, ABS_Y, litest_scale(d, ABS_Y, y)); litest_event(d, EV_SYN, SYN_REPORT, 0); + + return true; /* we handled the event */ } -static void touch_up(struct litest_device *d, unsigned int slot) +static bool +touch_up(struct litest_device *d, unsigned int slot) { assert(slot == 0); litest_event(d, EV_SYN, SYN_REPORT, 0); + + return true; /* we handled the event */ } static struct litest_device_interface interface = { diff --git a/test/litest-device-sony-vaio-keys.c b/test/litest-device-sony-vaio-keys.c new file mode 100644 index 0000000..753d5c3 --- /dev/null +++ b/test/litest-device-sony-vaio-keys.c @@ -0,0 +1,98 @@ +/* + * Copyright © 2020 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include + +#include "libinput-util.h" + +#include "litest.h" +#include "litest-int.h" + +/* Description taken from + * https://gitlab.freedesktop.org/libinput/libinput/-/issues/515 + */ + +static struct input_id input_id = { + .bustype = 0x10, + .vendor = 0x104d, + .product = 0x00, +}; + +static int events[] = { + EV_KEY, KEY_UP, + EV_KEY, KEY_DOWN, + EV_KEY, KEY_MUTE, + EV_KEY, KEY_VOLUMEDOWN, + EV_KEY, KEY_VOLUMEUP, + EV_KEY, KEY_HELP, + EV_KEY, KEY_PROG1, + EV_KEY, KEY_PROG2, + EV_KEY, KEY_BACK, + EV_KEY, KEY_EJECTCD, + EV_KEY, KEY_F13, + EV_KEY, KEY_F14, + EV_KEY, KEY_F15, + EV_KEY, KEY_F21, + EV_KEY, KEY_PROG3, + EV_KEY, KEY_PROG4, + EV_KEY, KEY_SUSPEND, + EV_KEY, KEY_CAMERA, + EV_KEY, KEY_BRIGHTNESSDOWN, + EV_KEY, KEY_BRIGHTNESSUP, + EV_KEY, KEY_MEDIA, + EV_KEY, KEY_SWITCHVIDEOMODE, + EV_KEY, KEY_BLUETOOTH, + EV_KEY, KEY_WLAN, + EV_KEY, BTN_THUMB, + EV_KEY, KEY_VENDOR, + EV_KEY, KEY_FULL_SCREEN, + EV_KEY, KEY_ZOOMIN, + EV_KEY, KEY_ZOOMOUT, + EV_KEY, KEY_FN, + EV_KEY, KEY_FN_ESC, + EV_KEY, KEY_FN_F8, + EV_KEY, KEY_FN_F11, + EV_KEY, KEY_FN_1, + EV_KEY, KEY_FN_2, + EV_KEY, KEY_FN_D, + EV_KEY, KEY_FN_E, + EV_KEY, KEY_FN_F, + EV_KEY, KEY_FN_S, + EV_KEY, KEY_FN_B, + + EV_MSC, MSC_SCAN, + -1, -1, +}; + +TEST_DEVICE("sony-vaio-keys", + .type = LITEST_SONY_VAIO_KEYS, + .features = LITEST_KEYS, + .interface = NULL, + + .name = "Sony Vaio Keys", + .id = &input_id, + .events = events, + .absinfo = NULL, +) diff --git a/test/litest-device-tablet-mode-switch.c b/test/litest-device-tablet-mode-switch.c new file mode 100644 index 0000000..e677ddf --- /dev/null +++ b/test/litest-device-tablet-mode-switch.c @@ -0,0 +1,66 @@ +/* + * Copyright © 2020 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include "litest.h" +#include "litest-int.h" + +static struct input_id input_id = { + .bustype = 0x18, + .vendor = 0x123, + .product = 0x456, +}; + +static int events[] = { + /* buttons are needed - the unreliable quirk removes SW_TABLET_MODE + * so we'd end up with a device with no seat caps and that won't get + * added */ + EV_KEY, BTN_LEFT, + EV_KEY, BTN_RIGHT, + EV_SW, SW_TABLET_MODE, + -1, -1, +}; + +static const char quirk_file[] = +"[litest unreliable tablet mode switch]\n" +"MatchName=litest Unreliable Tablet Mode Switch device\n" +"ModelTabletModeSwitchUnreliable=1\n"; + +TEST_DEVICE("tablet-mode-switch-unreliable", + .type = LITEST_TABLET_MODE_UNRELIABLE, + .features = LITEST_SWITCH, + .interface = NULL, + + .name = "Unreliable Tablet Mode Switch device", + .id = &input_id, + .events = events, + .absinfo = NULL, + + .quirk_file = quirk_file, + .udev_properties = { + { "ID_INPUT_SWITCH", "1" }, + { NULL }, + } +) + diff --git a/test/litest-device-uclogic-tablet.c b/test/litest-device-uclogic-tablet.c index 9b6c399..f9773ad 100644 --- a/test/litest-device-uclogic-tablet.c +++ b/test/litest-device-uclogic-tablet.c @@ -88,7 +88,7 @@ static int events[] = { TEST_DEVICE("uclogic-tablet", .type = LITEST_UCLOGIC_TABLET, - .features = LITEST_TABLET | LITEST_HOVER, + .features = LITEST_TABLET | LITEST_HOVER | LITEST_FORCED_PROXOUT, .interface = &interface, .name = "uclogic PenTablet Pen", diff --git a/test/litest-device-vmware-virtual-usb-mouse.c b/test/litest-device-vmware-virtual-usb-mouse.c index faefda3..81fd0b7 100644 --- a/test/litest-device-vmware-virtual-usb-mouse.c +++ b/test/litest-device-vmware-virtual-usb-mouse.c @@ -27,30 +27,37 @@ #include "litest-int.h" #include -static void touch_down(struct litest_device *d, unsigned int slot, - double x, double y) +static bool +touch_down(struct litest_device *d, unsigned int slot, double x, double y) { assert(slot == 0); litest_event(d, EV_ABS, ABS_X, litest_scale(d, ABS_X, x)); litest_event(d, EV_ABS, ABS_Y, litest_scale(d, ABS_Y, y)); litest_event(d, EV_SYN, SYN_REPORT, 0); + + return true; /* we handled the event */ } -static void touch_move(struct litest_device *d, unsigned int slot, - double x, double y) +static bool +touch_move(struct litest_device *d, unsigned int slot, double x, double y) { assert(slot == 0); litest_event(d, EV_ABS, ABS_X, litest_scale(d, ABS_X, x)); litest_event(d, EV_ABS, ABS_Y, litest_scale(d, ABS_Y, y)); litest_event(d, EV_SYN, SYN_REPORT, 0); + + return true; /* we handled the event */ } -static void touch_up(struct litest_device *d, unsigned int slot) +static bool +touch_up(struct litest_device *d, unsigned int slot) { assert(slot == 0); litest_event(d, EV_SYN, SYN_REPORT, 0); + + return true; /* we handled the event */ } static struct litest_device_interface interface = { diff --git a/test/litest-device-xen-virtual-pointer.c b/test/litest-device-xen-virtual-pointer.c index 44d3013..79c4c55 100644 --- a/test/litest-device-xen-virtual-pointer.c +++ b/test/litest-device-xen-virtual-pointer.c @@ -27,30 +27,37 @@ #include "litest-int.h" #include -static void touch_down(struct litest_device *d, unsigned int slot, - double x, double y) +static bool +touch_down(struct litest_device *d, unsigned int slot, double x, double y) { assert(slot == 0); litest_event(d, EV_ABS, ABS_X, litest_scale(d, ABS_X, x)); litest_event(d, EV_ABS, ABS_Y, litest_scale(d, ABS_Y, y)); litest_event(d, EV_SYN, SYN_REPORT, 0); + + return true; /* we handled the event */ } -static void touch_move(struct litest_device *d, unsigned int slot, - double x, double y) +static bool +touch_move(struct litest_device *d, unsigned int slot, double x, double y) { assert(slot == 0); litest_event(d, EV_ABS, ABS_X, litest_scale(d, ABS_X, x)); litest_event(d, EV_ABS, ABS_Y, litest_scale(d, ABS_Y, y)); litest_event(d, EV_SYN, SYN_REPORT, 0); + + return true; /* we handled the event */ } -static void touch_up(struct litest_device *d, unsigned int slot) +static bool +touch_up(struct litest_device *d, unsigned int slot) { assert(slot == 0); litest_event(d, EV_SYN, SYN_REPORT, 0); + + return true; /* we handled the event */ } static struct litest_device_interface interface = { diff --git a/test/litest-int.h b/test/litest-int.h index 598c8d0..c5b05e6 100644 --- a/test/litest-int.h +++ b/test/litest-int.h @@ -81,9 +81,9 @@ struct litest_test_device { }; struct litest_device_interface { - void (*touch_down)(struct litest_device *d, unsigned int slot, double x, double y); - void (*touch_move)(struct litest_device *d, unsigned int slot, double x, double y); - void (*touch_up)(struct litest_device *d, unsigned int slot); + bool (*touch_down)(struct litest_device *d, unsigned int slot, double x, double y); + bool (*touch_move)(struct litest_device *d, unsigned int slot, double x, double y); + bool (*touch_up)(struct litest_device *d, unsigned int slot); /** * Default value for the given EV_ABS axis. @@ -131,6 +131,16 @@ struct litest_device_interface { int max[2]; /* x/y axis maximum */ }; +struct path { + struct list link; + char *path; + int fd; +}; + +struct litest_context { + struct list paths; +}; + void litest_set_current_device(struct litest_device *device); int litest_scale(const struct litest_device *d, unsigned int axis, double val); void litest_generic_device_teardown(void); diff --git a/test/litest.c b/test/litest.c index 5b09ec4..a10d50d 100644 --- a/test/litest.c +++ b/test/litest.c @@ -46,6 +46,7 @@ #include #include #include +#include #include #if HAVE_LIBSYSTEMD #include @@ -65,7 +66,6 @@ #include #define UDEV_RULES_D "/run/udev/rules.d" -#define UDEV_RULE_PREFIX "99-litest-" #define UDEV_FUZZ_OVERRIDE_RULE_FILE UDEV_RULES_D \ "/91-litest-fuzz-override-REMOVEME-XXXXXX.rules" #define UDEV_TEST_DEVICE_RULE_FILE UDEV_RULES_D \ @@ -73,7 +73,7 @@ #define UDEV_DEVICE_GROUPS_FILE UDEV_RULES_D \ "/80-libinput-device-groups-litest-XXXXXX.rules" -static int jobs = 8; +static int jobs; static bool in_debugger = false; static bool verbose = false; static bool run_deviceless = false; @@ -81,6 +81,7 @@ static bool use_system_rules_quirks = false; const char *filter_test = NULL; const char *filter_device = NULL; const char *filter_group = NULL; +const char *xml_prefix = NULL; static struct quirks_context *quirks_context; struct created_file { @@ -241,7 +242,7 @@ struct test { struct list node; char *name; char *devname; - void *func; + const void *func; void *setup; void *teardown; @@ -262,6 +263,49 @@ struct litest_device *litest_current_device(void) return current_device; } +static void +grab_device(struct litest_device *device, bool mode) +{ + struct libinput *li = libinput_device_get_context(device->libinput_device); + struct litest_context *ctx = libinput_get_user_data(li); + struct udev_device *udev_device; + const char *devnode; + struct path *p; + + udev_device = libinput_device_get_udev_device(device->libinput_device); + litest_assert_ptr_notnull(udev_device); + + devnode = udev_device_get_devnode(udev_device); + + /* Note: in some tests we create multiple devices for the same path. + * This will only grab the first device in the list but we're using + * list_insert() so the first device is the latest that was + * initialized, so we should be good. + */ + list_for_each(p, &ctx->paths, link) { + if (streq(p->path, devnode)) { + int rc = ioctl(p->fd, EVIOCGRAB, (void*)mode ? 1 : 0); + ck_assert_int_gt(rc, -1); + udev_device_unref(udev_device); + return; + } + } + litest_abort_msg("Failed to find device %s to %sgrab\n", + devnode, mode ? "" : "un"); +} + +void +litest_grab_device(struct litest_device *device) +{ + grab_device(device, true); +} + +void +litest_ungrab_device(struct litest_device *device) +{ + grab_device(device, false); +} + void litest_set_current_device(struct litest_device *device) { current_device = device; @@ -307,7 +351,7 @@ litest_reload_udev_rules(void) static void litest_add_tcase_for_device(struct suite *suite, const char *funcname, - void *func, + const void *func, const struct litest_test_device *dev, const struct range *range) { @@ -328,7 +372,7 @@ litest_add_tcase_for_device(struct suite *suite, static void litest_add_tcase_no_device(struct suite *suite, - void *func, + const void *func, const char *funcname, const struct range *range) { @@ -336,6 +380,7 @@ litest_add_tcase_no_device(struct suite *suite, const char *test_name = funcname; if (filter_device && + strstr(test_name, filter_device) == NULL && fnmatch(filter_device, test_name, 0) != 0) return; @@ -353,7 +398,7 @@ litest_add_tcase_no_device(struct suite *suite, static void litest_add_tcase_deviceless(struct suite *suite, - void *func, + const void *func, const char *funcname, const struct range *range) { @@ -361,6 +406,7 @@ litest_add_tcase_deviceless(struct suite *suite, const char *test_name = funcname; if (filter_device && + strstr(test_name, filter_device) == NULL && fnmatch(filter_device, test_name, 0) != 0) return; @@ -393,7 +439,7 @@ get_suite(const char *name) bool found = false; ARRAY_FOR_EACH(allowed_suites, allowed) { - if (strneq(name, *allowed, strlen(*allowed))) { + if (strstartswith(name, *allowed)) { found = true; break; } @@ -418,7 +464,7 @@ get_suite(const char *name) static void litest_add_tcase(const char *suite_name, const char *funcname, - void *func, + const void *func, int64_t required, int64_t excluded, const struct range *range) @@ -430,10 +476,12 @@ litest_add_tcase(const char *suite_name, litest_assert(excluded >= LITEST_DEVICELESS); if (filter_test && + strstr(funcname, filter_test) == NULL && fnmatch(filter_test, funcname, 0) != 0) return; if (filter_group && + strstr(suite_name, filter_group) == NULL && fnmatch(filter_group, suite_name, 0) != 0) return; @@ -455,6 +503,7 @@ litest_add_tcase(const char *suite_name, continue; if (filter_device && + strstr(dev->shortname, filter_device) == NULL && fnmatch(filter_device, dev->shortname, 0) != 0) continue; if ((dev->features & required) != required || @@ -476,6 +525,7 @@ litest_add_tcase(const char *suite_name, continue; if (filter_device && + strstr(dev->shortname, filter_device) == NULL && fnmatch(filter_device, dev->shortname, 0) != 0) continue; @@ -498,7 +548,7 @@ litest_add_tcase(const char *suite_name, } void -_litest_add_no_device(const char *name, const char *funcname, void *func) +_litest_add_no_device(const char *name, const char *funcname, const void *func) { _litest_add(name, funcname, func, LITEST_DISABLE_DEVICE, LITEST_DISABLE_DEVICE); } @@ -506,7 +556,7 @@ _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 void *func, const struct range *range) { _litest_add_ranged(name, @@ -520,7 +570,7 @@ _litest_add_ranged_no_device(const char *name, void _litest_add_deviceless(const char *name, const char *funcname, - void *func) + const void *func) { _litest_add_ranged(name, funcname, @@ -533,7 +583,7 @@ _litest_add_deviceless(const char *name, void _litest_add(const char *name, const char *funcname, - void *func, + const void *func, int64_t required, int64_t excluded) { @@ -548,7 +598,7 @@ _litest_add(const char *name, void _litest_add_ranged(const char *name, const char *funcname, - void *func, + const void *func, int64_t required, int64_t excluded, const struct range *range) @@ -559,7 +609,7 @@ _litest_add_ranged(const char *name, void _litest_add_for_device(const char *name, const char *funcname, - void *func, + const void *func, enum litest_device_type type) { _litest_add_ranged_for_device(name, funcname, func, type, NULL); @@ -568,7 +618,7 @@ _litest_add_for_device(const char *name, void _litest_add_ranged_for_device(const char *name, const char *funcname, - void *func, + const void *func, enum litest_device_type type, const struct range *range) { @@ -579,16 +629,19 @@ _litest_add_ranged_for_device(const char *name, litest_assert(type < LITEST_NO_DEVICE); if (filter_test && + strstr(funcname, filter_test) == NULL && fnmatch(filter_test, funcname, 0) != 0) return; if (filter_group && + strstr(name, filter_group) == NULL && fnmatch(filter_group, name, 0) != 0) return; s = get_suite(name); list_for_each(dev, &devices, node) { if (filter_device && + strstr(dev->shortname, filter_device) == NULL && fnmatch(filter_device, dev->shortname, 0) != 0) { device_filtered = true; continue; @@ -667,9 +720,14 @@ litest_log_handler(struct libinput *libinput, /* valgrind is too slow and some of our offsets are too * short, don't abort if during a valgrind run we get a * negative offset */ - if ((!RUNNING_ON_VALGRIND && !in_debugger) || - !strstr(format, "offset negative")) - litest_abort_msg("libinput bug triggered, aborting.\n"); + if ((RUNNING_ON_VALGRIND && in_debugger) || + !strstr(format, "scheduled expiry is in the past")) { + /* noop */ + } else if (!strstr(format, "event processing lagging behind")) { + /* noop */ + } else { + litest_abort_msg("libinput bug triggered, aborting.\n"); + } } if (strstr(format, "Touch jump detected and discarded")) { @@ -682,6 +740,7 @@ litest_init_device_udev_rules(struct litest_test_device *dev, FILE *f) { const struct key_value_str *kv; static int count; + bool need_keyboard_builtin = false; if (dev->udev_properties[0].key == NULL) return; @@ -697,10 +756,27 @@ litest_init_device_udev_rules(struct litest_test_device *dev, FILE *f) kv = dev->udev_properties; while (kv->key) { fprintf(f, ", \\\n\tENV{%s}=\"%s\"", kv->key, kv->value); + if (strneq(kv->key, "EVDEV_ABS_", 10)) + need_keyboard_builtin = true; kv++; } - fprintf(f, "\n"); + + /* Special case: the udev keyboard builtin is only run for hwdb + * matches but we don't set any up in litest. So instead scan the + * device's udev properties for any EVDEV_ABS properties and where + * they exist, force a (re-)run of the keyboard builtin to set up + * the evdev device correctly. + * This needs to be done as separate rule apparently, otherwise the + * ENV variables aren't set yet by the time the builtin runs. + */ + if (need_keyboard_builtin) { + fprintf(f, "" + "ATTRS{name}==\"litest %s*\"," + " IMPORT{builtin}=\"keyboard\"\n", + dev->name); + } + fprintf(f, "LABEL=\"rule%d_end\"\n\n", count);; } @@ -715,9 +791,8 @@ litest_init_all_device_udev_rules(struct list *created_files) int fd; rc = xasprintf(&path, - "%s/%s-XXXXXX.rules", - UDEV_RULES_D, - UDEV_RULE_PREFIX); + "%s/99-litest-XXXXXX.rules", + UDEV_RULES_D); litest_assert_int_gt(rc, 0); fd = mkstemps(path, 6); @@ -737,13 +812,47 @@ litest_init_all_device_udev_rules(struct list *created_files) static int open_restricted(const char *path, int flags, void *userdata) { - int fd = open(path, flags); - return fd < 0 ? -errno : fd; + const char prefix[] = "/dev/input/event"; + struct litest_context *ctx = userdata; + struct path *p; + int fd; + + litest_assert_ptr_notnull(ctx); + + fd = open(path, flags); + if (fd < 0) + return -errno; + + if (strneq(path, prefix, strlen(prefix))) { + p = zalloc(sizeof *p); + p->path = safe_strdup(path); + p->fd = fd; + /* We specifically insert here so that the most-recently + * opened path is the first one in the list. This helps when + * we have multiple test devices with the same device path, + * the fd of the most recent device is the first one to get + * grabbed + */ + list_insert(&ctx->paths, &p->link); + } + + return fd; } static void close_restricted(int fd, void *userdata) { + struct litest_context *ctx = userdata; + struct path *p, *tmp; + + list_for_each_safe(p, tmp, &ctx->paths, link) { + if (p->fd != fd) + continue; + list_remove(&p->link); + free(p->path); + free(p); + } + close(fd); } @@ -821,6 +930,68 @@ quirk_log_handler(struct libinput *unused, vfprintf(stderr, format, args); } +static void +litest_export_xml(SRunner *sr, const char *xml_prefix) +{ + TestResult **results; + int nresults, nfailed; + char *filename; + int fd; + + /* This is the minimum-effort implementation here because its only + * real purpose is to make test logs look pretty in the gitlab CI. + * + * Which means: + * - there's no filename validation, if you supply a filename that + * mkstemps doesn't like, things go boom. + * - every fork writes out a separate junit.xml file. gitlab is better + * at collecting lots of files than I am at writing code to collect + * this across forks to write out only one file. + * - most of the content is pretty useless because libcheck only gives + * us minimal information. the libcheck XML file has more info like + * the duration of each test but it's more complicated to extract + * and we don't need it for now. + */ + filename = safe_strdup(xml_prefix); + fd = mkstemps(filename, 4); + + results = srunner_results(sr); + nresults = srunner_ntests_run(sr); + nfailed = srunner_ntests_failed(sr); + + dprintf(fd, "\n"); + dprintf(fd, "\n", + filename, + nresults, + nfailed); + dprintf(fd, " \n"); + for (int i = 0; i < nresults; i++) { + TestResult *r = results[i]; + + dprintf(fd, " \n", + tr_tcname(r), + tr_tcname(r), + tr_rtype(r) == CK_PASS ? "/" : ""); + if (tr_rtype(r) != CK_PASS) { + dprintf(fd, " \n", + tr_lfile(r), + tr_lno(r)); + dprintf(fd, " %s:%d\n", tr_lfile(r), tr_lno(r)); + dprintf(fd, " %s\n", tr_tcname(r)); + dprintf(fd, "\n"); + dprintf(fd, " %s\n", tr_msg(r)); + dprintf(fd, " \n"); + dprintf(fd, " \n"); + } + } + dprintf(fd, " \n"); + dprintf(fd, "\n"); + + free(results); + close(fd); + free(filename); +} + static int litest_run_suite(struct list *tests, int which, int max, int error_fd) { @@ -918,6 +1089,10 @@ litest_run_suite(struct list *tests, int which, int max, int error_fd) goto out; srunner_run_all(sr, CK_ENV); + if (xml_prefix) + litest_export_xml(sr, xml_prefix); + + failed = srunner_ntests_failed(sr); if (failed) { TestResult **trs; @@ -1036,7 +1211,7 @@ inhibit(void) &error, &m, "ssss", - "handle-lid-switch:handle-power-key:handle-suspend-key:handle-hibernate-key", + "sleep:shutdown:handle-lid-switch:handle-power-key:handle-suspend-key:handle-hibernate-key", "libinput test-suite runner", "testing in progress", "block"); @@ -1294,6 +1469,10 @@ litest_init_device_quirk_file(const char *data_dir, return safe_strdup(path); } +static int is_quirks_file(const struct dirent *dir) { + return strendswith(dir->d_name, ".quirks"); +} + /** * Install the quirks from the quirks/ source directory. */ @@ -1301,30 +1480,30 @@ static void litest_install_source_quirks(struct list *created_files_list, const char *dirname) { - const char *quirksdir = "quirks/"; - char **quirks, **q; + struct dirent **namelist; + int ndev; - quirks = strv_from_string(LIBINPUT_QUIRKS_FILES, ":"); - litest_assert(quirks); + ndev = scandir(LIBINPUT_QUIRKS_SRCDIR, + &namelist, + is_quirks_file, + versionsort); + litest_assert_int_ge(ndev, 0); - q = quirks; - while (*q) { + for (int idx = 0; idx < ndev; idx++) { struct created_file *file; char *filename; char dest[PATH_MAX]; char src[PATH_MAX]; - litest_assert(strneq(*q, quirksdir, strlen(quirksdir))); - filename = &(*q)[strlen(quirksdir)]; - + filename = namelist[idx]->d_name; snprintf(src, sizeof(src), "%s/%s", LIBINPUT_QUIRKS_SRCDIR, filename); snprintf(dest, sizeof(dest), "%s/%s", dirname, filename); file = litest_copy_file(dest, src, NULL, true); list_append(created_files_list, &file->link); - q++; + free(namelist[idx]); } - strv_free(quirks); + free(namelist); } /** @@ -1518,8 +1697,13 @@ litest_create(enum litest_device_type which, struct libinput * litest_create_context(void) { - struct libinput *libinput = - libinput_path_create_context(&interface, NULL); + struct libinput *libinput; + struct litest_context *ctx; + + ctx = zalloc(sizeof *ctx); + list_init(&ctx->paths); + + libinput = libinput_path_create_context(&interface, ctx); litest_assert_notnull(libinput); libinput_log_set_handler(libinput, litest_log_handler); @@ -1530,6 +1714,23 @@ litest_create_context(void) } void +litest_destroy_context(struct libinput *li) +{ + struct path *p, *tmp; + struct litest_context *ctx; + + + ctx = libinput_get_user_data(li); + litest_assert_ptr_notnull(ctx); + libinput_unref(li); + + list_for_each_safe(p, tmp, &ctx->paths, link) { + litest_abort_msg("Device paths should be removed by now"); + } + free(ctx); +} + +void litest_disable_log_handler(struct libinput *libinput) { libinput_log_set_handler(libinput, NULL); @@ -1697,9 +1898,7 @@ udev_wait_for_device_event(struct udev_monitor *udev_monitor, } udev_syspath = udev_device_get_syspath(udev_device); - if (udev_syspath && strneq(udev_syspath, - syspath, - strlen(syspath))) + if (udev_syspath && strstartswith(udev_syspath, syspath)) break; udev_device_unref(udev_device); @@ -1734,7 +1933,7 @@ litest_delete_device(struct litest_device *d) } if (d->owns_context) { libinput_dispatch(d->libinput); - libinput_unref(d->libinput); + litest_destroy_context(d->libinput); } close(libevdev_get_fd(d->evdev)); libevdev_free(d->evdev); @@ -1877,10 +2076,11 @@ slot_start(struct litest_device *d, send_btntool(d, !touching); - if (d->interface->touch_down) { - d->interface->touch_down(d, slot, x, y); - return; - } + /* If the test device overrides touch_down and says it didn't + * handle the event, let's continue normally */ + if (d->interface->touch_down && + d->interface->touch_down(d, slot, x, y)) + return; for (ev = d->interface->touch_down_events; ev && (int16_t)ev->type != -1 && (int16_t)ev->code != -1; @@ -1914,10 +2114,9 @@ slot_move(struct litest_device *d, { struct input_event *ev; - if (d->interface->touch_move) { - d->interface->touch_move(d, slot, x, y); + if (d->interface->touch_move && + d->interface->touch_move(d, slot, x, y)) return; - } for (ev = d->interface->touch_move_events; ev && (int16_t)ev->type != -1 && (int16_t)ev->code != -1; @@ -1959,8 +2158,8 @@ touch_up(struct litest_device *d, unsigned int slot) send_btntool(d, false); - if (d->interface->touch_up) { - d->interface->touch_up(d, slot); + 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; @@ -2496,6 +2695,27 @@ litest_button_scroll(struct litest_device *dev, } void +litest_button_scroll_locked(struct litest_device *dev, + unsigned int button, + double dx, double dy) +{ + struct libinput *li = dev->libinput; + + litest_button_click_debounced(dev, li, button, 1); + litest_button_click_debounced(dev, li, button, 0); + + libinput_dispatch(li); + litest_timeout_buttonscroll(); + libinput_dispatch(li); + + litest_event(dev, EV_REL, REL_X, dx); + litest_event(dev, EV_REL, REL_Y, dy); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + + libinput_dispatch(li); +} + +void litest_keyboard_key(struct litest_device *d, unsigned int key, bool is_press) { struct input_event *ev; @@ -2877,6 +3097,9 @@ litest_event_type_str(enum libinput_event_type type) case LIBINPUT_EVENT_TABLET_PAD_STRIP: str = "TABLET PAD STRIP"; break; + case LIBINPUT_EVENT_TABLET_PAD_KEY: + str = "TABLET PAD KEY"; + break; case LIBINPUT_EVENT_SWITCH_TOGGLE: str = "SWITCH TOGGLE"; break; @@ -3404,21 +3627,30 @@ litest_assert_tablet_button_event(struct libinput *li, unsigned int button, libinput_event_destroy(event); } -void litest_assert_tablet_proximity_event(struct libinput *li, - enum libinput_tablet_tool_proximity_state state) + +struct libinput_event_tablet_tool * +litest_is_proximity_event(struct libinput_event *event, + enum libinput_tablet_tool_proximity_state state) { - struct libinput_event *event; struct libinput_event_tablet_tool *tev; enum libinput_event_type type = LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY; - litest_wait_for_event(li); - event = libinput_get_event(li); - litest_assert_notnull(event); litest_assert_event_type(event, type); tev = libinput_event_get_tablet_tool_event(event); litest_assert_int_eq(libinput_event_tablet_tool_get_proximity_state(tev), state); + return tev; +} + +void litest_assert_tablet_proximity_event(struct libinput *li, + enum libinput_tablet_tool_proximity_state state) +{ + struct libinput_event *event; + + litest_wait_for_event(li); + event = libinput_get_event(li); + litest_is_proximity_event(event, state); libinput_event_destroy(event); } @@ -3502,6 +3734,27 @@ litest_is_pad_strip_event(struct libinput_event *event, return p; } +struct libinput_event_tablet_pad * +litest_is_pad_key_event(struct libinput_event *event, + unsigned int key, + enum libinput_key_state state) +{ + struct libinput_event_tablet_pad *p; + enum libinput_event_type type = LIBINPUT_EVENT_TABLET_PAD_KEY; + + litest_assert(event != NULL); + litest_assert_event_type(event, type); + + p = libinput_event_get_tablet_pad_event(event); + litest_assert(p != NULL); + + litest_assert_int_eq(libinput_event_tablet_pad_get_key(p), key); + litest_assert_int_eq(libinput_event_tablet_pad_get_key_state(p), + state); + + return p; +} + struct libinput_event_switch * litest_is_switch_event(struct libinput_event *event, enum libinput_switch sw, @@ -3537,6 +3790,21 @@ litest_assert_pad_button_event(struct libinput *li, } void +litest_assert_pad_key_event(struct libinput *li, + unsigned int key, + enum libinput_key_state state) +{ + struct libinput_event *event; + struct libinput_event_tablet_pad *pev; + + litest_wait_for_event(li); + event = libinput_get_event(li); + + pev = litest_is_pad_key_event(event, key, state); + libinput_event_destroy(libinput_event_tablet_pad_get_base_event(pev)); +} + +void litest_assert_scroll(struct libinput *li, enum libinput_pointer_axis axis, int minimum_movement) @@ -3606,6 +3874,26 @@ litest_assert_only_typed_events(struct libinput *li, } void +litest_assert_no_typed_events(struct libinput *li, + enum libinput_event_type type) +{ + struct libinput_event *event; + + litest_assert(type != LIBINPUT_EVENT_NONE); + + libinput_dispatch(li); + event = libinput_get_event(li); + + while (event) { + litest_assert_int_ne(libinput_event_get_type(event), + type); + libinput_event_destroy(event); + libinput_dispatch(li); + event = libinput_get_event(li); + } +} + +void litest_assert_touch_sequence(struct libinput *li) { struct libinput_event *event; @@ -3999,6 +4287,7 @@ litest_parse_argv(int argc, char **argv) OPT_FILTER_DEVICE, OPT_FILTER_GROUP, OPT_FILTER_DEVICELESS, + OPT_XML_PREFIX, OPT_JOBS, OPT_LIST, OPT_VERBOSE, @@ -4008,6 +4297,7 @@ litest_parse_argv(int argc, char **argv) { "filter-device", 1, 0, OPT_FILTER_DEVICE }, { "filter-group", 1, 0, OPT_FILTER_GROUP }, { "filter-deviceless", 0, 0, OPT_FILTER_DEVICELESS }, + { "xml-output", 1, 0, OPT_XML_PREFIX }, { "jobs", 1, 0, OPT_JOBS }, { "list", 0, 0, OPT_LIST }, { "verbose", 0, 0, OPT_VERBOSE }, @@ -4060,6 +4350,10 @@ litest_parse_argv(int argc, char **argv) " Glob to filter on test groups\n" " --filter-deviceless=.... \n" " Glob to filter on tests that do not create test devices\n" + " --xml-output=/path/to/file-XXXXXXX.xml\n" + " Write test output in libcheck's XML format\n" + " to the given files. The file must match the format\n" + " prefix-XXXXXX.xml and only the prefix is your choice.\n" " --verbose\n" " Enable verbose output\n" " --jobs 8\n" @@ -4085,6 +4379,9 @@ litest_parse_argv(int argc, char **argv) case OPT_FILTER_GROUP: filter_group = optarg; break; + case OPT_XML_PREFIX: + xml_prefix = optarg; + break; case 'j': case OPT_JOBS: jobs = atoi(optarg); @@ -4252,6 +4549,10 @@ main(int argc, char **argv) if (in_debugger || RUNNING_ON_VALGRIND) setenv("CK_FORK", "no", 0); + jobs = get_nprocs(); + if (!RUNNING_ON_VALGRIND) + jobs *= 2; + mode = litest_parse_argv(argc, argv); if (mode == LITEST_MODE_ERROR) return EXIT_FAILURE; diff --git a/test/litest.h b/test/litest.h index 85a0a1f..1f4e609 100644 --- a/test/litest.h +++ b/test/litest.h @@ -300,6 +300,12 @@ enum litest_device_type { LITEST_DELL_CANVAS_TOTEM, LITEST_DELL_CANVAS_TOTEM_TOUCH, LITEST_WACOM_ISDV4_4200_PEN, + LITEST_ALPS_3FG, + LITEST_ELAN_TABLET, + LITEST_ABSINFO_OVERRIDE, + LITEST_TABLET_MODE_UNRELIABLE, + LITEST_KEYBOARD_LOGITECH_MEDIA_KEYBOARD_ELITE, + LITEST_SONY_VAIO_KEYS, }; #define LITEST_DEVICELESS -2 @@ -337,6 +343,7 @@ enum litest_device_type { #define LITEST_TOOL_MOUSE bit(29) #define LITEST_DIRECT bit(30) #define LITEST_TOTEM bit(31) +#define LITEST_FORCED_PROXOUT bit(32) /* this is a semi-mt device, so we keep track of the touches that the tests * send and modify them so that the first touch is always slot 0 and sends @@ -415,6 +422,7 @@ struct range { }; struct libinput *litest_create_context(void); +void litest_destroy_context(struct libinput *li); void litest_disable_log_handler(struct libinput *libinput); void litest_restore_log_handler(struct libinput *libinput); void litest_set_log_handler_bug(struct libinput *libinput); @@ -437,40 +445,40 @@ void litest_set_log_handler_bug(struct libinput *libinput); void _litest_add(const char *name, const char *funcname, - void *func, + const void *func, int64_t required_feature, int64_t excluded_feature); void _litest_add_ranged(const char *name, const char *funcname, - void *func, + const void *func, int64_t required, int64_t excluded, const struct range *range); void _litest_add_for_device(const char *name, const char *funcname, - void *func, + const void *func, enum litest_device_type type); void _litest_add_ranged_for_device(const char *name, const char *funcname, - void *func, + const void *func, enum litest_device_type type, const struct range *range); void _litest_add_no_device(const char *name, const char *funcname, - void *func); + const void *func); void _litest_add_ranged_no_device(const char *name, const char *funcname, - void *func, + const void *func, const struct range *range); void _litest_add_deviceless(const char *name, const char *funcname, - void *func); + const void *func); struct litest_device * litest_create_device(enum litest_device_type which); @@ -508,6 +516,12 @@ struct litest_device * litest_current_device(void); void +litest_grab_device(struct litest_device *d); + +void +litest_ungrab_device(struct litest_device *d); + +void litest_delete_device(struct litest_device *d); void @@ -663,6 +677,10 @@ void litest_button_scroll(struct litest_device *d, unsigned int button, double dx, double dy); +void +litest_button_scroll_locked(struct litest_device *d, + unsigned int button, + double dx, double dy); void litest_keyboard_key(struct litest_device *d, @@ -747,12 +765,20 @@ struct libinput_event_tablet_pad * litest_is_pad_strip_event(struct libinput_event *event, unsigned int number, enum libinput_tablet_pad_strip_axis_source source); +struct libinput_event_tablet_pad * +litest_is_pad_key_event(struct libinput_event *event, + unsigned int key, + enum libinput_key_state state); struct libinput_event_switch * litest_is_switch_event(struct libinput_event *event, enum libinput_switch sw, enum libinput_switch_state state); +struct libinput_event_tablet_tool * +litest_is_proximity_event(struct libinput_event *event, + enum libinput_tablet_tool_proximity_state state); + void litest_assert_key_event(struct libinput *li, unsigned int key, enum libinput_key_state state); @@ -772,6 +798,10 @@ litest_assert_only_typed_events(struct libinput *li, enum libinput_event_type type); void +litest_assert_no_typed_events(struct libinput *li, + enum libinput_event_type type); + +void litest_assert_tablet_button_event(struct libinput *li, unsigned int button, enum libinput_button_state state); @@ -788,6 +818,10 @@ void litest_assert_pad_button_event(struct libinput *li, unsigned int button, enum libinput_button_state state); +void +litest_assert_pad_key_event(struct libinput *li, + unsigned int key, + enum libinput_key_state state); struct libevdev_uinput * litest_create_uinput_device(const char *name, struct input_id *id, @@ -1133,4 +1167,34 @@ litest_send_file(int sock, int fd) return write(sock, buf, n); } +static inline int litest_slot_count(struct litest_device *dev) +{ + if (dev->which == LITEST_ALPS_3FG) + return 2; + + return libevdev_get_num_slots(dev->evdev); +} + +static inline bool +litest_has_palm_detect_size(struct litest_device *dev) +{ + double width, height; + unsigned int vendor; + unsigned int bustype; + int rc; + + vendor = libinput_device_get_id_vendor(dev->libinput_device); + bustype = libevdev_get_id_bustype(dev->evdev); + if (vendor == VENDOR_ID_WACOM) + return 0; + if (bustype == BUS_BLUETOOTH) + return 0; + if (vendor == VENDOR_ID_APPLE) + return 1; + + rc = libinput_device_get_size(dev->libinput_device, &width, &height); + + return rc == 0 && width >= 70; +} + #endif /* LITEST_H */ diff --git a/test/test-device.c b/test/test-device.c index 3f79201..3a4a6b5 100644 --- a/test/test-device.c +++ b/test/test-device.c @@ -179,6 +179,46 @@ START_TEST(device_disable) } END_TEST +START_TEST(device_disable_tablet) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_device *device; + enum libinput_config_status status; + struct axis_replacement axes[] = { + { ABS_DISTANCE, 10 }, + { ABS_PRESSURE, 0 }, + { -1, -1 } + }; + + device = dev->libinput_device; + + litest_drain_events(li); + + status = libinput_device_config_send_events_set_mode(device, + LIBINPUT_CONFIG_SEND_EVENTS_DISABLED); + ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS); + + /* no event from disabling */ + litest_assert_empty_queue(li); + + litest_tablet_proximity_in(dev, 60, 60, axes); + for (int i = 60; i < 70; i++) { + litest_tablet_motion(dev, i, i, axes); + libinput_dispatch(li); + } + litest_tablet_proximity_out(dev); + + litest_assert_empty_queue(li); + + /* no event from resuming */ + status = libinput_device_config_send_events_set_mode(device, + LIBINPUT_CONFIG_SEND_EVENTS_ENABLED); + ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS); + litest_assert_empty_queue(li); +} +END_TEST + START_TEST(device_disable_touchpad) { struct litest_device *dev = litest_current_device(); @@ -414,7 +454,7 @@ START_TEST(device_reenable_syspath_changed) litest_delete_device(litest_device); libinput_device_unref(device1); - libinput_unref(li); + litest_destroy_context(li); } END_TEST @@ -448,7 +488,7 @@ START_TEST(device_reenable_device_removed) litest_assert_empty_queue(li); libinput_device_unref(device); - libinput_unref(li); + litest_destroy_context(li); } END_TEST @@ -759,22 +799,6 @@ START_TEST(device_user_data) } END_TEST -static int open_restricted(const char *path, int flags, void *data) -{ - int fd; - fd = open(path, flags); - return fd < 0 ? -errno : fd; -} -static void close_restricted(int fd, void *data) -{ - close(fd); -} - -const struct libinput_interface simple_interface = { - .open_restricted = open_restricted, - .close_restricted = close_restricted, -}; - START_TEST(device_group_get) { struct litest_device *dev = litest_current_device(); @@ -819,7 +843,7 @@ START_TEST(device_group_ref) ck_assert_notnull(libinput_device_group_unref(group)); ck_assert(libinput_device_group_unref(group) == NULL); - libinput_unref(li); + litest_destroy_context(li); } END_TEST @@ -837,7 +861,7 @@ START_TEST(device_group_leak) EV_REL, REL_Y, -1); - li = libinput_path_create_context(&simple_interface, NULL); + li = litest_create_context(); device = libinput_path_add_device(li, libevdev_uinput_get_devnode(uinput)); @@ -847,7 +871,7 @@ START_TEST(device_group_leak) libinput_path_remove_device(device); libevdev_uinput_destroy(uinput); - libinput_unref(li); + litest_destroy_context(li); /* the device group leaks, check valgrind */ } @@ -870,7 +894,7 @@ START_TEST(abs_device_no_absx) libevdev_uinput_get_devnode(uinput)); litest_restore_log_handler(li); ck_assert(device == NULL); - libinput_unref(li); + litest_destroy_context(li); libevdev_uinput_destroy(uinput); } @@ -893,7 +917,7 @@ START_TEST(abs_device_no_absy) libevdev_uinput_get_devnode(uinput)); litest_restore_log_handler(li); ck_assert(device == NULL); - libinput_unref(li); + litest_destroy_context(li); libevdev_uinput_destroy(uinput); } @@ -919,7 +943,7 @@ START_TEST(abs_mt_device_no_absy) libevdev_uinput_get_devnode(uinput)); litest_restore_log_handler(li); ck_assert(device == NULL); - libinput_unref(li); + litest_destroy_context(li); libevdev_uinput_destroy(uinput); } @@ -945,7 +969,7 @@ START_TEST(abs_mt_device_no_absx) libevdev_uinput_get_devnode(uinput)); litest_restore_log_handler(li); ck_assert(device == NULL); - libinput_unref(li); + litest_destroy_context(li); libevdev_uinput_destroy(uinput); } @@ -986,7 +1010,7 @@ START_TEST(abs_device_no_range) assert_device_ignored(li, absinfo); litest_restore_log_handler(li); - libinput_unref(li); + litest_destroy_context(li); } END_TEST @@ -1014,7 +1038,7 @@ START_TEST(abs_mt_device_no_range) assert_device_ignored(li, absinfo); litest_restore_log_handler(li); - libinput_unref(li); + litest_destroy_context(li); } END_TEST @@ -1038,7 +1062,7 @@ START_TEST(abs_device_missing_res) assert_device_ignored(li, absinfo); litest_restore_log_handler(li); - libinput_unref(li); + litest_destroy_context(li); } END_TEST @@ -1065,7 +1089,7 @@ START_TEST(abs_mt_device_missing_res) assert_device_ignored(li, absinfo); litest_restore_log_handler(li); - libinput_unref(li); + litest_destroy_context(li); } END_TEST @@ -1099,7 +1123,7 @@ START_TEST(ignore_joystick) litest_assert_ptr_null(device); libevdev_uinput_destroy(uinput); litest_restore_log_handler(li); - libinput_unref(li); + litest_destroy_context(li); } END_TEST @@ -1137,7 +1161,7 @@ START_TEST(device_accelerometer) litest_assert_ptr_null(device); libevdev_uinput_destroy(uinput); litest_restore_log_handler(li); - libinput_unref(li); + litest_destroy_context(li); } END_TEST @@ -1185,7 +1209,7 @@ START_TEST(device_nonpointer_rel) } litest_restore_log_handler(li); - libinput_unref(li); + litest_destroy_context(li); libevdev_uinput_destroy(uinput); } END_TEST @@ -1225,7 +1249,7 @@ START_TEST(device_touchpad_rel) libinput_dispatch(li); } - libinput_unref(li); + litest_destroy_context(li); libevdev_uinput_destroy(uinput); } END_TEST @@ -1266,7 +1290,7 @@ START_TEST(device_touch_rel) } litest_restore_log_handler(li); - libinput_unref(li); + litest_destroy_context(li); libevdev_uinput_destroy(uinput); } END_TEST @@ -1302,7 +1326,7 @@ START_TEST(device_abs_rel) libinput_dispatch(li); } - libinput_unref(li); + litest_destroy_context(li); libevdev_uinput_destroy(uinput); } END_TEST @@ -1432,6 +1456,28 @@ START_TEST(device_capability_check_invalid) } END_TEST +START_TEST(device_capability_nocaps_ignored) +{ + struct libevdev_uinput *uinput; + struct libinput *li; + struct libinput_device *device; + + /* SW_PEN_INSERTED isn't handled in libinput so the device is + * processed but ends up without seat capabilities and is ignored. + */ + uinput = litest_create_uinput_device("test device", NULL, + EV_SW, SW_PEN_INSERTED, + -1); + li = litest_create_context(); + device = libinput_path_add_device(li, + libevdev_uinput_get_devnode(uinput)); + litest_assert_ptr_null(device); + + litest_destroy_context(li); + libevdev_uinput_destroy(uinput); +} +END_TEST + START_TEST(device_has_size) { struct litest_device *dev = litest_current_device(); @@ -1556,7 +1602,7 @@ START_TEST(device_button_down_remove) libinput_event_destroy(event); } - libinput_unref(li); + litest_destroy_context(li); ck_assert_int_eq(have_down, have_up); } } @@ -1573,6 +1619,7 @@ TEST_COLLECTION(device) litest_add("device:sendevents", device_sendevents_config_touchpad_superset, LITEST_TOUCHPAD, LITEST_TABLET); litest_add("device:sendevents", device_sendevents_config_default, LITEST_ANY, LITEST_TABLET); litest_add("device:sendevents", device_disable, LITEST_RELATIVE, LITEST_TABLET); + litest_add("device:sendevents", device_disable_tablet, LITEST_TABLET, LITEST_ANY); litest_add("device:sendevents", device_disable_touchpad, LITEST_TOUCHPAD, LITEST_TABLET); litest_add("device:sendevents", device_disable_touch, LITEST_TOUCH, LITEST_ANY); litest_add("device:sendevents", device_disable_touch_during_touch, LITEST_TOUCH, LITEST_ANY); @@ -1626,6 +1673,7 @@ TEST_COLLECTION(device) litest_add("device:capability", device_capability_at_least_one, LITEST_ANY, LITEST_ANY); litest_add("device:capability", device_capability_check_invalid, LITEST_ANY, LITEST_ANY); + litest_add_no_device("device:capability", device_capability_nocaps_ignored); litest_add("device:size", device_has_size, LITEST_TOUCHPAD, LITEST_ANY); litest_add("device:size", device_has_size, LITEST_TABLET, LITEST_ANY); diff --git a/test/test-gestures.c b/test/test-gestures.c index ff1df2b..d16175e 100644 --- a/test/test-gestures.c +++ b/test/test-gestures.c @@ -77,7 +77,7 @@ START_TEST(gestures_swipe_3fg) { -30, 30 }, }; - if (libevdev_get_num_slots(dev->evdev) < 3) + if (litest_slot_count(dev) < 3) return; dir_x = cardinals[cardinal][0]; @@ -176,7 +176,7 @@ START_TEST(gestures_swipe_3fg_btntool) { -30, 30 }, }; - if (libevdev_get_num_slots(dev->evdev) > 2 || + if (litest_slot_count(dev) > 2 || !libevdev_has_event_code(dev->evdev, EV_KEY, BTN_TOOL_TRIPLETAP) || !libinput_device_has_capability(dev->libinput_device, LIBINPUT_DEVICE_CAP_GESTURE)) @@ -266,7 +266,7 @@ START_TEST(gestures_swipe_3fg_btntool_pinch_like) struct libinput_event *event; struct libinput_event_gesture *gevent; - if (libevdev_get_num_slots(dev->evdev) > 2 || + if (litest_slot_count(dev) > 2 || !libevdev_has_event_code(dev->evdev, EV_KEY, BTN_TOOL_TRIPLETAP) || !libinput_device_has_capability(dev->libinput_device, LIBINPUT_DEVICE_CAP_GESTURE)) @@ -287,15 +287,13 @@ START_TEST(gestures_swipe_3fg_btntool_pinch_like) libinput_dispatch(li); event = libinput_get_event(li); - gevent = litest_is_gesture_event(event, - LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN, - 3); + litest_is_gesture_event(event, LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN, 3); libinput_event_destroy(event); while ((event = libinput_get_event(li)) != NULL) { - gevent = litest_is_gesture_event(event, - LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE, - 3); + litest_is_gesture_event(event, + LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE, + 3); libinput_event_destroy(event); } @@ -332,7 +330,7 @@ START_TEST(gestures_swipe_4fg) }; int i; - if (libevdev_get_num_slots(dev->evdev) < 4) + if (litest_slot_count(dev) < 4) return; dir_x = cardinals[cardinal][0]; @@ -458,7 +456,7 @@ START_TEST(gestures_swipe_4fg_btntool) { -30, 30 }, }; - if (libevdev_get_num_slots(dev->evdev) > 2 || + if (litest_slot_count(dev) > 2 || !libevdev_has_event_code(dev->evdev, EV_KEY, BTN_TOOL_QUADTAP) || !libinput_device_has_capability(dev->libinput_device, LIBINPUT_DEVICE_CAP_GESTURE)) @@ -564,7 +562,7 @@ START_TEST(gestures_pinch) { -30, 30 }, }; - if (libevdev_get_num_slots(dev->evdev) < 2 || + if (litest_slot_count(dev) < 2 || !libinput_device_has_capability(dev->libinput_device, LIBINPUT_DEVICE_CAP_GESTURE)) return; @@ -676,7 +674,7 @@ START_TEST(gestures_pinch_3fg) { -30, 30 }, }; - if (libevdev_get_num_slots(dev->evdev) < 3) + if (litest_slot_count(dev) < 3) return; dir_x = cardinals[cardinal][0]; @@ -781,7 +779,7 @@ START_TEST(gestures_pinch_4fg) { -30, 30 }, }; - if (libevdev_get_num_slots(dev->evdev) < 4) + if (litest_slot_count(dev) < 4) return; dir_x = cardinals[cardinal][0]; @@ -892,7 +890,7 @@ START_TEST(gestures_spread) { -30, 30 }, }; - if (libevdev_get_num_slots(dev->evdev) < 2 || + if (litest_slot_count(dev) < 2 || !libinput_device_has_capability(dev->libinput_device, LIBINPUT_DEVICE_CAP_GESTURE)) return; @@ -987,7 +985,7 @@ START_TEST(gestures_time_usec) struct libinput_event_gesture *gevent; uint64_t time_usec; - if (libevdev_get_num_slots(dev->evdev) < 3) + if (litest_slot_count(dev) < 3) return; litest_drain_events(li); @@ -1016,7 +1014,7 @@ START_TEST(gestures_3fg_buttonarea_scroll) struct litest_device *dev = litest_current_device(); struct libinput *li = dev->libinput; - if (libevdev_get_num_slots(dev->evdev) < 3) + if (litest_slot_count(dev) < 3) return; litest_enable_buttonareas(dev); @@ -1042,7 +1040,7 @@ START_TEST(gestures_3fg_buttonarea_scroll_btntool) struct litest_device *dev = litest_current_device(); struct libinput *li = dev->libinput; - if (libevdev_get_num_slots(dev->evdev) > 2) + if (litest_slot_count(dev) > 2) return; litest_enable_buttonareas(dev); diff --git a/test/test-keyboard.c b/test/test-keyboard.c index f5b566d..6da6f85 100644 --- a/test/test-keyboard.c +++ b/test/test-keyboard.c @@ -96,7 +96,7 @@ START_TEST(keyboard_seat_key_count) for (i = 0; i < num_devices; ++i) litest_delete_device(devices[i]); - libinput_unref(libinput); + litest_destroy_context(libinput); } END_TEST @@ -157,8 +157,8 @@ START_TEST(keyboard_ignore_no_pressed_release) litest_assert_empty_queue(libinput); litest_delete_device(dev); - libinput_unref(libinput); - libinput_unref(unused_libinput); + litest_destroy_context(libinput); + litest_destroy_context(unused_libinput); } END_TEST @@ -258,7 +258,7 @@ START_TEST(keyboard_key_auto_release) ck_assert_int_eq(keys[i].released, 1); } - libinput_unref(libinput); + litest_destroy_context(libinput); } END_TEST diff --git a/test/test-log.c b/test/test-log.c index 7b035d3..47b4deb 100644 --- a/test/test-log.c +++ b/test/test-log.c @@ -35,22 +35,6 @@ static int log_handler_called; static struct libinput *log_handler_context; -static int open_restricted(const char *path, int flags, void *data) -{ - int fd; - fd = open(path, flags); - return fd < 0 ? -errno : fd; -} -static void close_restricted(int fd, void *data) -{ - close(fd); -} - -static const struct libinput_interface simple_interface = { - .open_restricted = open_restricted, - .close_restricted = close_restricted, -}; - static void simple_log_handler(struct libinput *libinput, enum libinput_log_priority priority, @@ -68,12 +52,12 @@ START_TEST(log_default_priority) enum libinput_log_priority pri; struct libinput *li; - li = libinput_path_create_context(&simple_interface, NULL); + li = litest_create_context(); pri = libinput_log_get_priority(li); ck_assert_int_eq(pri, LIBINPUT_LOG_PRIORITY_ERROR); - libinput_unref(li); + litest_destroy_context(li); } END_TEST @@ -84,7 +68,7 @@ START_TEST(log_handler_invoked) log_handler_context = NULL; log_handler_called = 0; - li = libinput_path_create_context(&simple_interface, NULL); + li = litest_create_context(); libinput_log_set_priority(li, LIBINPUT_LOG_PRIORITY_DEBUG); libinput_log_set_handler(li, simple_log_handler); @@ -94,7 +78,7 @@ START_TEST(log_handler_invoked) ck_assert_int_gt(log_handler_called, 0); - libinput_unref(li); + litest_destroy_context(li); log_handler_context = NULL; log_handler_called = 0; @@ -107,7 +91,7 @@ START_TEST(log_handler_NULL) log_handler_called = 0; - li = libinput_path_create_context(&simple_interface, NULL); + li = litest_create_context(); libinput_log_set_priority(li, LIBINPUT_LOG_PRIORITY_DEBUG); libinput_log_set_handler(li, NULL); @@ -115,7 +99,7 @@ START_TEST(log_handler_NULL) ck_assert_int_eq(log_handler_called, 0); - libinput_unref(li); + litest_destroy_context(li); log_handler_called = 0; } @@ -128,7 +112,7 @@ START_TEST(log_priority) log_handler_context = NULL; log_handler_called = 0; - li = libinput_path_create_context(&simple_interface, NULL); + li = litest_create_context(); libinput_log_set_priority(li, LIBINPUT_LOG_PRIORITY_ERROR); libinput_log_set_handler(li, simple_log_handler); log_handler_context = li; @@ -143,7 +127,7 @@ START_TEST(log_priority) libinput_path_add_device(li, "/dev/input/event0"); ck_assert_int_gt(log_handler_called, 1); - libinput_unref(li); + litest_destroy_context(li); log_handler_context = NULL; log_handler_called = 0; diff --git a/test/test-misc.c b/test/test-misc.c index 9aa3c9e..b72a77e 100644 --- a/test/test-misc.c +++ b/test/test-misc.c @@ -105,7 +105,7 @@ START_TEST(event_conversion_device_notify) EV_KEY, BTN_MIDDLE, EV_KEY, BTN_LEFT, -1, -1); - li = libinput_path_create_context(&simple_interface, NULL); + li = litest_create_context(); litest_restore_log_handler(li); /* use the default litest handler */ libinput_path_add_device(li, libevdev_uinput_get_devnode(uinput)); @@ -144,7 +144,7 @@ START_TEST(event_conversion_device_notify) libinput_event_destroy(event); } - libinput_unref(li); + litest_destroy_context(li); libevdev_uinput_destroy(uinput); ck_assert_int_gt(device_added, 0); @@ -660,7 +660,7 @@ static void timer_offset_warning(struct libinput *libinput, int *warning_triggered = (int*)libinput_get_user_data(libinput); if (priority == LIBINPUT_LOG_PRIORITY_ERROR && - strstr(format, "offset negative")) + strstr(format, "scheduled expiry is in the past")) (*warning_triggered)++; } @@ -669,6 +669,7 @@ START_TEST(timer_offset_bug_warning) struct litest_device *dev = litest_current_device(); struct libinput *li = dev->libinput; int warning_triggered = 0; + void *old_user_data; litest_enable_tap(dev->libinput_device); litest_drain_events(li); @@ -678,6 +679,7 @@ START_TEST(timer_offset_bug_warning) litest_timeout_tap(); + old_user_data = libinput_get_user_data(li); libinput_set_user_data(li, &warning_triggered); libinput_log_set_handler(li, timer_offset_warning); libinput_dispatch(li); @@ -685,6 +687,49 @@ START_TEST(timer_offset_bug_warning) /* triggered for touch down and touch up */ ck_assert_int_eq(warning_triggered, 2); litest_restore_log_handler(li); + + libinput_set_user_data(li, old_user_data); +} +END_TEST + +static void timer_delay_warning(struct libinput *libinput, + enum libinput_log_priority priority, + const char *format, + va_list args) +{ + int *warning_triggered = (int*)libinput_get_user_data(libinput); + + if (priority == LIBINPUT_LOG_PRIORITY_ERROR && + strstr(format, "event processing lagging behind by")) + (*warning_triggered)++; +} + + +START_TEST(timer_delay_bug_warning) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + int warning_triggered = 0; + void *old_user_data; + + old_user_data = libinput_get_user_data(li); + litest_drain_events(li); + + for (int i = 0; i < 10; i++) { + litest_button_click(dev, BTN_LEFT, true); + libinput_dispatch(li); + litest_button_click(dev, BTN_LEFT, false); + msleep(11); + + libinput_set_user_data(li, &warning_triggered); + libinput_log_set_handler(li, timer_delay_warning); + libinput_dispatch(li); + } + + + ck_assert_int_ge(warning_triggered, 1); + litest_restore_log_handler(li); + libinput_set_user_data(li, old_user_data); } END_TEST @@ -756,7 +801,86 @@ START_TEST(timer_flush) litest_delete_device(keyboard); litest_delete_device(touchpad); - libinput_unref(li); + + litest_destroy_context(li); +} +END_TEST + +START_TEST(udev_absinfo_override) +{ + struct litest_device *dev = litest_current_device(); + struct libevdev *evdev = dev->evdev; + const struct input_absinfo *abs; + struct udev_device *ud; + struct udev_list_entry *entry; + bool found_x = false, found_y = false, + found_mt_x = false, found_mt_y = false; + + ud = libinput_device_get_udev_device(dev->libinput_device); + ck_assert_notnull(ud); + + /* Custom checks for this special litest device only */ + + entry = udev_device_get_properties_list_entry(ud); + while (entry) { + const char *key, *value; + + key = udev_list_entry_get_name(entry); + value = udev_list_entry_get_value(entry); + + if (streq(key, "EVDEV_ABS_00")) { + found_x = true; + ck_assert(streq(value, "1:1000:100:10")); + } + if (streq(key, "EVDEV_ABS_01")) { + found_y = true; + ck_assert(streq(value, "2:2000:200:20")); + } + if (streq(key, "EVDEV_ABS_35")) { + found_mt_x = true; + ck_assert(streq(value, "3:3000:300:30")); + } + if (streq(key, "EVDEV_ABS_36")) { + found_mt_y = true; + ck_assert(streq(value, "4:4000:400:40")); + } + + entry = udev_list_entry_get_next(entry); + } + udev_device_unref(ud); + + ck_assert(found_x); + ck_assert(found_y); + ck_assert(found_mt_x); + ck_assert(found_mt_y); + + abs = libevdev_get_abs_info(evdev, ABS_X); + ck_assert_int_eq(abs->minimum, 1); + ck_assert_int_eq(abs->maximum, 1000); + ck_assert_int_eq(abs->resolution, 100); + /* if everything goes well, we override the fuzz to 0 */ + ck_assert_int_eq(abs->fuzz, 0); + + abs = libevdev_get_abs_info(evdev, ABS_Y); + ck_assert_int_eq(abs->minimum, 2); + ck_assert_int_eq(abs->maximum, 2000); + ck_assert_int_eq(abs->resolution, 200); + /* if everything goes well, we override the fuzz to 0 */ + ck_assert_int_eq(abs->fuzz, 0); + + abs = libevdev_get_abs_info(evdev, ABS_MT_POSITION_X); + ck_assert_int_eq(abs->minimum, 3); + ck_assert_int_eq(abs->maximum, 3000); + ck_assert_int_eq(abs->resolution, 300); + /* if everything goes well, we override the fuzz to 0 */ + ck_assert_int_eq(abs->fuzz, 0); + + abs = libevdev_get_abs_info(evdev, ABS_MT_POSITION_Y); + ck_assert_int_eq(abs->minimum, 4); + ck_assert_int_eq(abs->maximum, 4000); + ck_assert_int_eq(abs->resolution, 400); + /* if everything goes well, we override the fuzz to 0 */ + ck_assert_int_eq(abs->fuzz, 0); } END_TEST @@ -777,7 +901,10 @@ TEST_COLLECTION(misc) litest_add_deviceless("config:status string", config_status_string); litest_add_for_device("timer:offset-warning", timer_offset_bug_warning, LITEST_SYNAPTICS_TOUCHPAD); + litest_add_for_device("timer:delay-warning", timer_delay_bug_warning, LITEST_MOUSE); litest_add_no_device("timer:flush", timer_flush); litest_add_no_device("misc:fd", fd_no_event_leak); + + litest_add_for_device("misc:system", udev_absinfo_override, LITEST_ABSINFO_OVERRIDE); } diff --git a/test/test-pad.c b/test/test-pad.c index 6ba8925..9eba718 100644 --- a/test/test-pad.c +++ b/test/test-pad.c @@ -916,6 +916,72 @@ START_TEST(pad_mode_group_has_no_toggle) } END_TEST +static bool +pad_has_keys(struct litest_device *dev) +{ + struct libevdev *evdev = dev->evdev; + + return (libevdev_has_event_code(evdev, EV_KEY, KEY_BUTTONCONFIG) || + libevdev_has_event_code(evdev, EV_KEY, KEY_ONSCREEN_KEYBOARD) || + libevdev_has_event_code(evdev, EV_KEY, KEY_CONTROLPANEL)); +} + +static void +pad_key_down(struct litest_device *dev, unsigned int which) +{ + litest_event(dev, EV_ABS, ABS_MISC, 15); + litest_event(dev, EV_KEY, which, 1); + litest_event(dev, EV_SYN, SYN_REPORT, 0); +} + +static void +pad_key_up(struct litest_device *dev, unsigned int which) +{ + litest_event(dev, EV_ABS, ABS_MISC, 0); + litest_event(dev, EV_KEY, which, 0); + litest_event(dev, EV_SYN, SYN_REPORT, 0); +} + +START_TEST(pad_keys) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + unsigned int key; + + if (!pad_has_keys(dev)) + return; + + litest_drain_events(li); + + key = KEY_BUTTONCONFIG; + pad_key_down(dev, key); + libinput_dispatch(li); + litest_assert_pad_key_event(li, key, LIBINPUT_KEY_STATE_PRESSED); + + pad_key_up(dev, key); + libinput_dispatch(li); + litest_assert_pad_key_event(li, key, LIBINPUT_KEY_STATE_RELEASED); + + key = KEY_ONSCREEN_KEYBOARD; + pad_key_down(dev, key); + libinput_dispatch(li); + litest_assert_pad_key_event(li, key, LIBINPUT_KEY_STATE_PRESSED); + + pad_key_up(dev, key); + libinput_dispatch(li); + litest_assert_pad_key_event(li, key, LIBINPUT_KEY_STATE_RELEASED); + + key = KEY_CONTROLPANEL; + pad_key_down(dev, key); + libinput_dispatch(li); + litest_assert_pad_key_event(li, key, LIBINPUT_KEY_STATE_PRESSED); + + pad_key_up(dev, key); + libinput_dispatch(li); + litest_assert_pad_key_event(li, key, LIBINPUT_KEY_STATE_RELEASED); +} +END_TEST + TEST_COLLECTION(tablet_pad) { litest_add("pad:cap", pad_cap, LITEST_TABLET_PAD, LITEST_ANY); @@ -950,4 +1016,6 @@ TEST_COLLECTION(tablet_pad) litest_add("pad:modes", pad_mode_group_has, LITEST_TABLET_PAD, LITEST_ANY); litest_add("pad:modes", pad_mode_group_has_invalid, LITEST_TABLET_PAD, LITEST_ANY); litest_add("pad:modes", pad_mode_group_has_no_toggle, LITEST_TABLET_PAD, LITEST_ANY); + + litest_add("pad:keys", pad_keys, LITEST_TABLET_PAD, LITEST_ANY); } diff --git a/test/test-path.c b/test/test-path.c index 7e78dd4..9c6f25c 100644 --- a/test/test-path.c +++ b/test/test-path.c @@ -456,7 +456,6 @@ END_TEST START_TEST(path_add_invalid_path) { struct libinput *li; - struct libinput_event *event; struct libinput_device *device; li = litest_create_context(); @@ -468,10 +467,9 @@ START_TEST(path_add_invalid_path) libinput_dispatch(li); - while ((event = libinput_get_event(li))) - ck_abort(); + litest_assert_empty_queue(li); - libinput_unref(li); + litest_destroy_context(li); } END_TEST @@ -1009,7 +1007,7 @@ START_TEST(path_ignore_device) device = libinput_path_add_device(li, path); ck_assert(device == NULL); - libinput_unref(li); + litest_destroy_context(li); litest_delete_device(dev); } END_TEST diff --git a/test/test-pointer.c b/test/test-pointer.c index 6561489..c89a15a 100644 --- a/test/test-pointer.c +++ b/test/test-pointer.c @@ -312,7 +312,7 @@ START_TEST(pointer_absolute_initial_state) libinput_event_destroy(ev2); } - libinput_unref(libinput2); + litest_destroy_context(libinput2); } END_TEST @@ -490,7 +490,7 @@ START_TEST(pointer_button_auto_release) ck_assert_int_eq(buttons[i].released, 1); } - libinput_unref(libinput); + litest_destroy_context(libinput); } END_TEST @@ -602,33 +602,6 @@ out: return angle; } -static enum libinput_pointer_axis_source -wheel_source(struct litest_device *dev, int which) -{ - struct udev_device *d; - bool is_tilt = false; - - d = libinput_device_get_udev_device(dev->libinput_device); - litest_assert_ptr_notnull(d); - - switch(which) { - case REL_WHEEL: - is_tilt = !!udev_device_get_property_value(d, "MOUSE_WHEEL_TILT_VERTICAL"); - break; - case REL_HWHEEL: - is_tilt = !!udev_device_get_property_value(d, "MOUSE_WHEEL_TILT_HORIZONTAL"); - break; - default: - litest_abort_msg("Invalid source axis %d\n", which); - break; - } - - udev_device_unref(d); - return is_tilt ? - LIBINPUT_POINTER_AXIS_SOURCE_WHEEL_TILT : - LIBINPUT_POINTER_AXIS_SOURCE_WHEEL; -} - static void test_wheel_event(struct litest_device *dev, int which, int amount) { @@ -641,7 +614,7 @@ test_wheel_event(struct litest_device *dev, int which, int amount) double scroll_step, expected, discrete; scroll_step = wheel_click_angle(dev, which); - source = wheel_source(dev, which); + source = LIBINPUT_POINTER_AXIS_SOURCE_WHEEL; expected = amount * scroll_step; discrete = amount; @@ -888,7 +861,7 @@ START_TEST(pointer_seat_button_count) for (i = 0; i < num_devices; ++i) litest_delete_device(devices[i]); - libinput_unref(libinput); + litest_destroy_context(libinput); } END_TEST @@ -1171,8 +1144,8 @@ START_TEST(pointer_scroll_button_middle_emulation) litest_drain_events(li); - litest_button_click_debounced(dev, li, BTN_LEFT, 1); - litest_button_click_debounced(dev, li, BTN_RIGHT, 1); + litest_button_click(dev, BTN_LEFT, 1); + litest_button_click(dev, BTN_RIGHT, 1); libinput_dispatch(li); litest_timeout_buttonscroll(); libinput_dispatch(li); @@ -1184,8 +1157,8 @@ START_TEST(pointer_scroll_button_middle_emulation) libinput_dispatch(li); - litest_button_click_debounced(dev, li, BTN_LEFT, 0); - litest_button_click_debounced(dev, li, BTN_RIGHT, 0); + litest_button_click(dev, BTN_LEFT, 0); + litest_button_click(dev, BTN_RIGHT, 0); libinput_dispatch(li); litest_assert_scroll(li, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL, -1); @@ -1223,7 +1196,504 @@ START_TEST(pointer_scroll_button_device_remove_while_down) litest_delete_device(dev); libinput_dispatch(li); - libinput_unref(li); + litest_destroy_context(li); +} +END_TEST + +static void +litest_enable_scroll_button_lock(struct litest_device *dev, + unsigned int button) +{ + struct libinput_device *device = dev->libinput_device; + enum libinput_config_status status; + + status = libinput_device_config_scroll_set_method(device, + LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN); + ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS); + + status = libinput_device_config_scroll_set_button(device, button); + ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS); + + status = libinput_device_config_scroll_set_button_lock(device, + LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_ENABLED); + ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS); +} + +START_TEST(pointer_scroll_button_lock) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + + litest_enable_scroll_button_lock(dev, BTN_LEFT); + litest_disable_middleemu(dev); + + litest_drain_events(li); + + litest_button_click_debounced(dev, li, BTN_LEFT, true); + litest_button_click_debounced(dev, li, BTN_LEFT, false); + + litest_assert_empty_queue(li); + + litest_timeout_buttonscroll(); + libinput_dispatch(li); + + for (int i = 0; i < 10; i++) { + litest_event(dev, EV_REL, REL_X, 1); + litest_event(dev, EV_REL, REL_Y, 6); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + } + + libinput_dispatch(li); + + litest_button_click_debounced(dev, li, BTN_LEFT, true); + litest_button_click_debounced(dev, li, BTN_LEFT, false); + libinput_dispatch(li); + + litest_assert_scroll(li, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL, 6); + + litest_assert_empty_queue(li); + + /* back to motion */ + for (int i = 0; i < 10; i++) { + litest_event(dev, EV_REL, REL_X, 1); + litest_event(dev, EV_REL, REL_Y, 6); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + } + litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION); +} +END_TEST + +START_TEST(pointer_scroll_button_lock_defaults) +{ + struct litest_device *dev = litest_current_device(); + enum libinput_config_scroll_button_lock_state state; + + state = libinput_device_config_scroll_get_button_lock(dev->libinput_device); + ck_assert_int_eq(state, LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_DISABLED); + state = libinput_device_config_scroll_get_default_button_lock(dev->libinput_device); + ck_assert_int_eq(state, LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_DISABLED); +} +END_TEST + +START_TEST(pointer_scroll_button_lock_config) +{ + struct litest_device *dev = litest_current_device(); + enum libinput_config_status status; + enum libinput_config_scroll_button_lock_state state; + + state = libinput_device_config_scroll_get_button_lock(dev->libinput_device); + ck_assert_int_eq(state, LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_DISABLED); + state = libinput_device_config_scroll_get_default_button_lock(dev->libinput_device); + ck_assert_int_eq(state, LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_DISABLED); + + status = libinput_device_config_scroll_set_button_lock(dev->libinput_device, + LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_DISABLED); + ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS); + state = libinput_device_config_scroll_get_button_lock(dev->libinput_device); + ck_assert_int_eq(state, LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_DISABLED); + + + status = libinput_device_config_scroll_set_button_lock(dev->libinput_device, + LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_ENABLED); + ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS); + state = libinput_device_config_scroll_get_button_lock(dev->libinput_device); + ck_assert_int_eq(state, LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_ENABLED); + + status = libinput_device_config_scroll_set_button_lock(dev->libinput_device, + LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_ENABLED + 1); + ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_INVALID); +} +END_TEST + +START_TEST(pointer_scroll_button_lock_enable_while_down) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + + litest_disable_middleemu(dev); + litest_drain_events(li); + + litest_button_click_debounced(dev, li, BTN_LEFT, true); + + /* Enable lock while button is down */ + litest_enable_scroll_button_lock(dev, BTN_LEFT); + + litest_assert_button_event(li, BTN_LEFT, LIBINPUT_BUTTON_STATE_PRESSED); + litest_assert_empty_queue(li); + + litest_button_click_debounced(dev, li, BTN_LEFT, false); + litest_assert_button_event(li, BTN_LEFT, LIBINPUT_BUTTON_STATE_RELEASED); + litest_assert_empty_queue(li); + + for (int i = 0; i < 10; i++) { + litest_event(dev, EV_REL, REL_X, 1); + litest_event(dev, EV_REL, REL_Y, 6); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + } + + /* no scrolling yet */ + litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION); + + /* but on the next button press we scroll lock */ + litest_button_click_debounced(dev, li, BTN_LEFT, true); + litest_button_click_debounced(dev, li, BTN_LEFT, false); + libinput_dispatch(li); + litest_timeout_buttonscroll(); + libinput_dispatch(li); + + for (int i = 0; i < 10; i++) { + litest_event(dev, EV_REL, REL_X, 1); + litest_event(dev, EV_REL, REL_Y, 6); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + } + + litest_button_click_debounced(dev, li, BTN_LEFT, true); + litest_button_click_debounced(dev, li, BTN_LEFT, false); + + litest_assert_scroll(li, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL, 6); + + litest_assert_empty_queue(li); + + /* back to motion */ + for (int i = 0; i < 10; i++) { + litest_event(dev, EV_REL, REL_X, 1); + litest_event(dev, EV_REL, REL_Y, 6); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + } + litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION); +} +END_TEST + +START_TEST(pointer_scroll_button_lock_enable_while_down_just_lock) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + + litest_disable_middleemu(dev); + litest_drain_events(li); + + /* switch method first, but enable lock when we already have a + * button down */ + libinput_device_config_scroll_set_method(dev->libinput_device, + LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN); + libinput_device_config_scroll_set_button(dev->libinput_device, + BTN_LEFT); + + litest_button_click_debounced(dev, li, BTN_LEFT, true); + libinput_device_config_scroll_set_button_lock(dev->libinput_device, + LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_ENABLED); + + litest_button_click_debounced(dev, li, BTN_LEFT, false); + 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); + + for (int i = 0; i < 10; i++) { + litest_event(dev, EV_REL, REL_X, 1); + litest_event(dev, EV_REL, REL_Y, 6); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + } + + /* no scrolling yet */ + litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION); + + /* but on the next button press we scroll lock */ + litest_button_click_debounced(dev, li, BTN_LEFT, true); + litest_button_click_debounced(dev, li, BTN_LEFT, false); + libinput_dispatch(li); + litest_timeout_buttonscroll(); + libinput_dispatch(li); + + for (int i = 0; i < 10; i++) { + litest_event(dev, EV_REL, REL_X, 1); + litest_event(dev, EV_REL, REL_Y, 6); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + } + + litest_button_click_debounced(dev, li, BTN_LEFT, true); + litest_button_click_debounced(dev, li, BTN_LEFT, false); + + litest_assert_scroll(li, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL, 6); + + litest_assert_empty_queue(li); + + /* back to motion */ + for (int i = 0; i < 10; i++) { + litest_event(dev, EV_REL, REL_X, 1); + litest_event(dev, EV_REL, REL_Y, 6); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + } + litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION); +} +END_TEST + +START_TEST(pointer_scroll_button_lock_otherbutton) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + + litest_disable_middleemu(dev); + litest_drain_events(li); + + litest_enable_scroll_button_lock(dev, BTN_LEFT); + + litest_button_click_debounced(dev, li, BTN_LEFT, true); + litest_button_click_debounced(dev, li, BTN_LEFT, false); + litest_assert_empty_queue(li); + litest_timeout_buttonscroll(); + libinput_dispatch(li); + + /* other button passes on normally */ + litest_button_click_debounced(dev, li, BTN_RIGHT, true); + litest_button_click_debounced(dev, li, BTN_RIGHT, false); + litest_assert_button_event(li, BTN_RIGHT, LIBINPUT_BUTTON_STATE_PRESSED); + litest_assert_button_event(li, BTN_RIGHT, LIBINPUT_BUTTON_STATE_RELEASED); + litest_assert_empty_queue(li); + + for (int i = 0; i < 10; i++) { + litest_event(dev, EV_REL, REL_X, 1); + litest_event(dev, EV_REL, REL_Y, 6); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + } + litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_AXIS); + + /* other button passes on normally */ + litest_button_click_debounced(dev, li, BTN_RIGHT, true); + litest_button_click_debounced(dev, li, BTN_RIGHT, false); + litest_assert_button_event(li, BTN_RIGHT, LIBINPUT_BUTTON_STATE_PRESSED); + litest_assert_button_event(li, BTN_RIGHT, LIBINPUT_BUTTON_STATE_RELEASED); + + /* stop scroll lock */ + litest_button_click_debounced(dev, li, BTN_LEFT, true); + litest_button_click_debounced(dev, li, BTN_LEFT, false); + litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_AXIS); + + /* other button passes on normally */ + litest_button_click_debounced(dev, li, BTN_RIGHT, true); + litest_button_click_debounced(dev, li, BTN_RIGHT, false); + litest_assert_button_event(li, BTN_RIGHT, LIBINPUT_BUTTON_STATE_PRESSED); + litest_assert_button_event(li, BTN_RIGHT, LIBINPUT_BUTTON_STATE_RELEASED); + + litest_assert_empty_queue(li); +} +END_TEST + +START_TEST(pointer_scroll_button_lock_enable_while_otherbutton_down) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + + litest_disable_middleemu(dev); + litest_drain_events(li); + + litest_button_click_debounced(dev, li, BTN_RIGHT, true); + litest_timeout_middlebutton(); + litest_drain_events(li); + + /* Enable lock while button is down */ + litest_enable_scroll_button_lock(dev, BTN_LEFT); + + /* We only enable once we go to a neutral state so this still counts + * as normal button event */ + for (int twice = 0; twice < 2; twice++) { + litest_button_click_debounced(dev, li, BTN_LEFT, true); + litest_button_click_debounced(dev, li, BTN_LEFT, false); + litest_assert_button_event(li, BTN_LEFT, LIBINPUT_BUTTON_STATE_PRESSED); + litest_assert_button_event(li, BTN_LEFT, LIBINPUT_BUTTON_STATE_RELEASED); + + for (int i = 0; i < 10; i++) { + litest_event(dev, EV_REL, REL_X, 1); + litest_event(dev, EV_REL, REL_Y, 6); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + } + litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION); + } + + litest_button_click_debounced(dev, li, BTN_RIGHT, false); + litest_assert_button_event(li, BTN_RIGHT, LIBINPUT_BUTTON_STATE_RELEASED); + litest_assert_empty_queue(li); + + /* now we should trigger it */ + litest_button_click_debounced(dev, li, BTN_LEFT, true); + litest_button_click_debounced(dev, li, BTN_LEFT, false); + litest_timeout_buttonscroll(); + litest_assert_empty_queue(li); + + for (int i = 0; i < 10; i++) { + litest_event(dev, EV_REL, REL_X, 1); + litest_event(dev, EV_REL, REL_Y, 6); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + } + + litest_button_click_debounced(dev, li, BTN_LEFT, true); + litest_button_click_debounced(dev, li, BTN_LEFT, false); + litest_assert_scroll(li, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL, 6); + litest_assert_empty_queue(li); + + /* back to motion */ + for (int i = 0; i < 10; i++) { + litest_event(dev, EV_REL, REL_X, 1); + litest_event(dev, EV_REL, REL_Y, 6); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + } + litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION); +} +END_TEST + +enum mb_buttonorder { + LLRR, /* left down, left up, r down, r up */ + LRLR, /* left down, right down, left up, right up */ + LRRL, + RRLL, + RLRL, + RLLR, + _MB_BUTTONORDER_COUNT +}; + +START_TEST(pointer_scroll_button_lock_middlebutton) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + enum mb_buttonorder buttonorder = _i; /* ranged test */ + + if (!libinput_device_config_middle_emulation_is_available(dev->libinput_device)) + return; + + litest_enable_middleemu(dev); + + litest_enable_scroll_button_lock(dev, BTN_LEFT); + litest_drain_events(li); + + /* We expect scroll lock to work only where left and right are never + * held down simultaneously. Everywhere else we expect middle button + * instead. + */ + switch (buttonorder) { + case LLRR: + litest_button_click_debounced(dev, li, BTN_LEFT, true); + litest_button_click_debounced(dev, li, BTN_LEFT, false); + litest_button_click_debounced(dev, li, BTN_RIGHT, true); + litest_button_click_debounced(dev, li, BTN_RIGHT, false); + break; + case LRLR: + litest_button_click_debounced(dev, li, BTN_LEFT, true); + litest_button_click_debounced(dev, li, BTN_RIGHT, true); + litest_button_click_debounced(dev, li, BTN_LEFT, false); + litest_button_click_debounced(dev, li, BTN_RIGHT, false); + break; + case LRRL: + litest_button_click_debounced(dev, li, BTN_LEFT, true); + litest_button_click_debounced(dev, li, BTN_RIGHT, true); + litest_button_click_debounced(dev, li, BTN_RIGHT, false); + litest_button_click_debounced(dev, li, BTN_LEFT, false); + break; + case RRLL: + litest_button_click_debounced(dev, li, BTN_RIGHT, true); + litest_button_click_debounced(dev, li, BTN_RIGHT, false); + litest_button_click_debounced(dev, li, BTN_LEFT, true); + litest_button_click_debounced(dev, li, BTN_LEFT, false); + break; + case RLRL: + litest_button_click_debounced(dev, li, BTN_RIGHT, true); + litest_button_click_debounced(dev, li, BTN_LEFT, true); + litest_button_click_debounced(dev, li, BTN_RIGHT, false); + litest_button_click_debounced(dev, li, BTN_LEFT, false); + break; + case RLLR: + litest_button_click_debounced(dev, li, BTN_RIGHT, true); + litest_button_click_debounced(dev, li, BTN_LEFT, true); + litest_button_click_debounced(dev, li, BTN_LEFT, false); + litest_button_click_debounced(dev, li, BTN_RIGHT, false); + break; + default: + abort(); + } + + libinput_dispatch(li); + litest_timeout_middlebutton(); + litest_timeout_buttonscroll(); + libinput_dispatch(li); + + /* motion events are the same for all of them */ + for (int i = 0; i < 10; i++) { + litest_event(dev, EV_REL, REL_X, 1); + litest_event(dev, EV_REL, REL_Y, 6); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + } + + libinput_dispatch(li); + + switch (buttonorder) { + case LLRR: + case RRLL: + litest_button_click_debounced(dev, li, BTN_LEFT, true); + litest_button_click_debounced(dev, li, BTN_LEFT, false); + break; + default: + break; + } + + libinput_dispatch(li); + + switch (buttonorder) { + case LLRR: + case RRLL: + litest_assert_button_event(li, BTN_RIGHT, + LIBINPUT_BUTTON_STATE_PRESSED); + litest_assert_button_event(li, BTN_RIGHT, + LIBINPUT_BUTTON_STATE_RELEASED); + litest_assert_scroll(li, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL, 6); + litest_assert_empty_queue(li); + break; + case LRLR: + case LRRL: + case RLRL: + case RLLR: + litest_assert_button_event(li, BTN_MIDDLE, + LIBINPUT_BUTTON_STATE_PRESSED); + litest_assert_button_event(li, BTN_MIDDLE, + LIBINPUT_BUTTON_STATE_RELEASED); + litest_assert_only_typed_events(li, + LIBINPUT_EVENT_POINTER_MOTION); + break; + default: + abort(); + } + +} +END_TEST + +START_TEST(pointer_scroll_button_lock_doubleclick_nomove) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + + litest_disable_middleemu(dev); + litest_enable_scroll_button_lock(dev, BTN_LEFT); + litest_drain_events(li); + + /* double click without move in between counts as single click */ + litest_button_click_debounced(dev, li, BTN_LEFT, true); + litest_button_click_debounced(dev, li, BTN_LEFT, false); + litest_assert_empty_queue(li); + litest_button_click_debounced(dev, li, BTN_LEFT, true); + litest_button_click_debounced(dev, li, BTN_LEFT, false); + + 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); + + /* But a non-scroll button it should work normally */ + litest_button_click_debounced(dev, li, BTN_RIGHT, true); + litest_button_click_debounced(dev, li, BTN_RIGHT, false); + litest_button_click_debounced(dev, li, BTN_RIGHT, true); + litest_button_click_debounced(dev, li, BTN_RIGHT, false); + litest_assert_button_event(li, BTN_RIGHT, LIBINPUT_BUTTON_STATE_PRESSED); + litest_assert_button_event(li, BTN_RIGHT, LIBINPUT_BUTTON_STATE_RELEASED); + litest_assert_button_event(li, BTN_RIGHT, LIBINPUT_BUTTON_STATE_PRESSED); + litest_assert_button_event(li, BTN_RIGHT, LIBINPUT_BUTTON_STATE_RELEASED); + litest_assert_empty_queue(li); + } END_TEST @@ -1447,39 +1917,6 @@ START_TEST(pointer_accel_profile_defaults) } END_TEST -START_TEST(pointer_accel_profile_defaults_noprofile) -{ - struct litest_device *dev = litest_current_device(); - struct libinput_device *device = dev->libinput_device; - enum libinput_config_status status; - enum libinput_config_accel_profile profile; - uint32_t profiles; - - ck_assert(libinput_device_config_accel_is_available(device)); - - profile = libinput_device_config_accel_get_default_profile(device); - ck_assert_int_eq(profile, LIBINPUT_CONFIG_ACCEL_PROFILE_NONE); - - profile = libinput_device_config_accel_get_profile(device); - ck_assert_int_eq(profile, LIBINPUT_CONFIG_ACCEL_PROFILE_NONE); - - profiles = libinput_device_config_accel_get_profiles(device); - ck_assert_int_eq(profiles, LIBINPUT_CONFIG_ACCEL_PROFILE_NONE); - - status = libinput_device_config_accel_set_profile(device, - LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT); - ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_UNSUPPORTED); - profile = libinput_device_config_accel_get_profile(device); - ck_assert_int_eq(profile, LIBINPUT_CONFIG_ACCEL_PROFILE_NONE); - - status = libinput_device_config_accel_set_profile(device, - LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE); - ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_UNSUPPORTED); - profile = libinput_device_config_accel_get_profile(device); - ck_assert_int_eq(profile, LIBINPUT_CONFIG_ACCEL_PROFILE_NONE); -} -END_TEST - START_TEST(pointer_accel_profile_invalid) { struct litest_device *dev = litest_current_device(); @@ -2657,7 +3094,7 @@ START_TEST(debounce_remove_device_button_up) litest_timeout_debounce(); libinput_dispatch(li); - libinput_unref(li); + litest_destroy_context(li); } END_TEST @@ -2682,7 +3119,7 @@ START_TEST(debounce_remove_device_button_down) litest_timeout_debounce(); libinput_dispatch(li); - libinput_unref(li); + litest_destroy_context(li); } END_TEST @@ -2691,6 +3128,7 @@ TEST_COLLECTION(pointer) struct range axis_range = {ABS_X, ABS_Y + 1}; struct range compass = {0, 7}; /* cardinal directions */ struct range buttons = {BTN_LEFT, BTN_TASK + 1}; + struct range buttonorder = {0, _MB_BUTTONORDER_COUNT}; litest_add("pointer:motion", pointer_motion_relative, LITEST_RELATIVE, LITEST_POINTINGSTICK); litest_add_for_device("pointer:motion", pointer_motion_relative_zero, LITEST_MOUSE); @@ -2709,6 +3147,17 @@ TEST_COLLECTION(pointer) litest_add("pointer:scroll", pointer_scroll_button_no_event_before_timeout, LITEST_RELATIVE|LITEST_BUTTON, LITEST_ANY); litest_add("pointer:scroll", pointer_scroll_button_middle_emulation, LITEST_RELATIVE|LITEST_BUTTON, LITEST_ANY); litest_add("pointer:scroll", pointer_scroll_button_device_remove_while_down, LITEST_ANY, LITEST_RELATIVE|LITEST_BUTTON); + + litest_add("pointer:scroll", pointer_scroll_button_lock, LITEST_RELATIVE|LITEST_BUTTON, LITEST_ANY); + litest_add("pointer:scroll", pointer_scroll_button_lock_defaults, LITEST_RELATIVE|LITEST_BUTTON, LITEST_ANY); + litest_add("pointer:scroll", pointer_scroll_button_lock_config, LITEST_RELATIVE|LITEST_BUTTON, LITEST_ANY); + litest_add("pointer:scroll", pointer_scroll_button_lock_enable_while_down, LITEST_RELATIVE|LITEST_BUTTON, LITEST_ANY); + litest_add("pointer:scroll", pointer_scroll_button_lock_enable_while_down_just_lock, LITEST_RELATIVE|LITEST_BUTTON, LITEST_ANY); + litest_add("pointer:scroll", pointer_scroll_button_lock_otherbutton, LITEST_RELATIVE|LITEST_BUTTON, LITEST_ANY); + litest_add("pointer:scroll", pointer_scroll_button_lock_enable_while_otherbutton_down, LITEST_RELATIVE|LITEST_BUTTON, LITEST_ANY); + litest_add_ranged("pointer:scroll", pointer_scroll_button_lock_middlebutton, LITEST_RELATIVE|LITEST_BUTTON, LITEST_ANY, &buttonorder); + litest_add("pointer:scroll", pointer_scroll_button_lock_doubleclick_nomove, LITEST_RELATIVE|LITEST_BUTTON, LITEST_ANY); + litest_add("pointer:scroll", pointer_scroll_nowheel_defaults, LITEST_RELATIVE|LITEST_BUTTON, LITEST_WHEEL); litest_add_for_device("pointer:scroll", pointer_scroll_defaults_logitech_marble , LITEST_LOGITECH_TRACKBALL); litest_add("pointer:scroll", pointer_scroll_natural_defaults, LITEST_WHEEL, LITEST_TABLET); @@ -2731,7 +3180,7 @@ TEST_COLLECTION(pointer) 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_POINTINGSTICK); litest_add("pointer:accel", pointer_accel_profile_defaults, LITEST_RELATIVE, LITEST_TOUCHPAD); - litest_add("pointer:accel", pointer_accel_profile_defaults_noprofile, LITEST_TOUCHPAD, LITEST_ANY); + litest_add("pointer:accel", pointer_accel_profile_defaults, LITEST_TOUCHPAD, LITEST_ANY); litest_add("pointer:accel", pointer_accel_profile_invalid, LITEST_RELATIVE, LITEST_ANY); litest_add("pointer:accel", pointer_accel_profile_noaccel, LITEST_ANY, LITEST_TOUCHPAD|LITEST_RELATIVE|LITEST_TABLET); litest_add("pointer:accel", pointer_accel_profile_flat_motion_relative, LITEST_RELATIVE, LITEST_TOUCHPAD); diff --git a/test/test-quirks.c b/test/test-quirks.c index 2cb5b85..b40b06d 100644 --- a/test/test-quirks.c +++ b/test/test-quirks.c @@ -63,6 +63,7 @@ make_data_dir(const char *file_content) litest_assert_int_eq(rc, (int)(strlen(dirname) + 16)); fp = fopen(filename, "w+"); + litest_assert_notnull(fp); rc = fputs(file_content, fp); fclose(fp); litest_assert_int_ge(rc, 0); @@ -1312,7 +1313,7 @@ START_TEST(quirks_model_alps) bool exists, value = false; q = dev->quirks; - exists = quirks_get_bool(q, QUIRK_MODEL_ALPS_TOUCHPAD, &value); + exists = quirks_get_bool(q, QUIRK_MODEL_ALPS_SERIAL_TOUCHPAD, &value); if (strstr(libinput_device_get_name(device), "ALPS")) { ck_assert(exists); diff --git a/test/test-switch.c b/test/test-switch.c index 29212e8..4dc06f1 100644 --- a/test/test-switch.c +++ b/test/test-switch.c @@ -24,6 +24,7 @@ #include #include +#include #include #include "libinput-util.h" @@ -33,20 +34,28 @@ static inline bool switch_has_lid(struct litest_device *dev) { return libinput_device_switch_has_switch(dev->libinput_device, - LIBINPUT_SWITCH_LID); + LIBINPUT_SWITCH_LID) > 0; } static inline bool switch_has_tablet_mode(struct litest_device *dev) { return libinput_device_switch_has_switch(dev->libinput_device, - LIBINPUT_SWITCH_TABLET_MODE); + LIBINPUT_SWITCH_TABLET_MODE) > 0; } START_TEST(switch_has_cap) { struct litest_device *dev = litest_current_device(); + /* Need to check for this specific device here because the + * unreliable tablet mode switch removes the capability too */ + if (dev->which == LITEST_TABLET_MODE_UNRELIABLE) { + ck_assert(!libinput_device_has_capability(dev->libinput_device, + LIBINPUT_DEVICE_CAP_SWITCH)); + return; + } + ck_assert(libinput_device_has_capability(dev->libinput_device, LIBINPUT_DEVICE_CAP_SWITCH)); @@ -66,16 +75,33 @@ START_TEST(switch_has_lid_switch) } END_TEST +static bool +tablet_mode_switch_is_reliable(struct litest_device *dev) +{ + bool is_unreliable = false; + + quirks_get_bool(dev->quirks, + QUIRK_MODEL_TABLET_MODE_SWITCH_UNRELIABLE, + &is_unreliable); + + return !is_unreliable; +} + START_TEST(switch_has_tablet_mode_switch) { struct litest_device *dev = litest_current_device(); + int has_switch; if (!libevdev_has_event_code(dev->evdev, EV_SW, SW_TABLET_MODE)) return; - ck_assert_int_eq(libinput_device_switch_has_switch(dev->libinput_device, - LIBINPUT_SWITCH_TABLET_MODE), - 1); + has_switch = libinput_device_switch_has_switch(dev->libinput_device, + LIBINPUT_SWITCH_TABLET_MODE); + + if (!tablet_mode_switch_is_reliable(dev)) + ck_assert_int_ne(has_switch, 1); + else + ck_assert_int_eq(has_switch, 1); } END_TEST @@ -88,10 +114,11 @@ START_TEST(switch_toggle) litest_drain_events(li); + litest_grab_device(dev); litest_switch_action(dev, sw, LIBINPUT_SWITCH_STATE_ON); libinput_dispatch(li); - if (libinput_device_switch_has_switch(dev->libinput_device, sw)) { + if (libinput_device_switch_has_switch(dev->libinput_device, sw) > 0) { event = libinput_get_event(li); litest_is_switch_event(event, sw, LIBINPUT_SWITCH_STATE_ON); libinput_event_destroy(event); @@ -102,13 +129,14 @@ START_TEST(switch_toggle) litest_switch_action(dev, sw, LIBINPUT_SWITCH_STATE_OFF); libinput_dispatch(li); - if (libinput_device_switch_has_switch(dev->libinput_device, sw)) { + if (libinput_device_switch_has_switch(dev->libinput_device, sw) > 0) { event = libinput_get_event(li); litest_is_switch_event(event, sw, LIBINPUT_SWITCH_STATE_OFF); libinput_event_destroy(event); } litest_assert_empty_queue(li); + litest_ungrab_device(dev); } END_TEST @@ -119,11 +147,12 @@ START_TEST(switch_toggle_double) struct libinput_event *event; enum libinput_switch sw = _i; /* ranged test */ - if (!libinput_device_switch_has_switch(dev->libinput_device, sw)) + if (libinput_device_switch_has_switch(dev->libinput_device, sw) <= 0) return; litest_drain_events(li); + litest_grab_device(dev); litest_switch_action(dev, sw, LIBINPUT_SWITCH_STATE_ON); libinput_dispatch(li); @@ -137,6 +166,7 @@ START_TEST(switch_toggle_double) libinput_dispatch(li); litest_assert_empty_queue(li); + litest_ungrab_device(dev); } END_TEST @@ -163,13 +193,15 @@ START_TEST(switch_down_on_init) struct libinput_event *event; enum libinput_switch sw = _i; /* ranged test */ - if (!libinput_device_switch_has_switch(dev->libinput_device, sw)) + if (libinput_device_switch_has_switch(dev->libinput_device, sw) <= 0) return; if (sw == LIBINPUT_SWITCH_LID && !lid_switch_is_reliable(dev)) return; + litest_grab_device(dev); litest_switch_action(dev, sw, LIBINPUT_SWITCH_STATE_ON); + litest_ungrab_device(dev); /* need separate context to test */ li = litest_create_context(); @@ -195,8 +227,7 @@ START_TEST(switch_down_on_init) libinput_event_destroy(event); litest_assert_empty_queue(li); - libinput_unref(li); - + litest_destroy_context(li); } END_TEST @@ -207,13 +238,15 @@ START_TEST(switch_not_down_on_init) struct libinput_event *event; enum libinput_switch sw = LIBINPUT_SWITCH_LID; - if (!libinput_device_switch_has_switch(dev->libinput_device, sw)) + if (libinput_device_switch_has_switch(dev->libinput_device, sw) <= 0) return; if (sw == LIBINPUT_SWITCH_LID && lid_switch_is_reliable(dev)) return; + litest_grab_device(dev); litest_switch_action(dev, sw, LIBINPUT_SWITCH_STATE_ON); + litest_ungrab_device(dev); /* need separate context to test */ li = litest_create_context(); @@ -229,7 +262,7 @@ START_TEST(switch_not_down_on_init) litest_switch_action(dev, sw, LIBINPUT_SWITCH_STATE_OFF); litest_assert_empty_queue(li); - libinput_unref(li); + litest_destroy_context(li); } END_TEST @@ -248,13 +281,15 @@ START_TEST(switch_disable_touchpad) struct libinput *li = sw->libinput; enum libinput_switch which = _i; /* ranged test */ - if (!libinput_device_switch_has_switch(sw->libinput_device, which)) + if (libinput_device_switch_has_switch(sw->libinput_device, which) <= 0) return; touchpad = switch_init_paired_touchpad(li); litest_disable_tap(touchpad->libinput_device); litest_drain_events(li); + litest_grab_device(sw); + /* switch is on - no events */ litest_switch_action(sw, which, LIBINPUT_SWITCH_STATE_ON); litest_assert_only_typed_events(li, LIBINPUT_EVENT_SWITCH_TOGGLE); @@ -274,6 +309,7 @@ START_TEST(switch_disable_touchpad) litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION); litest_delete_device(touchpad); + litest_ungrab_device(sw); } END_TEST @@ -284,7 +320,7 @@ START_TEST(switch_disable_touchpad_during_touch) struct libinput *li = sw->libinput; enum libinput_switch which = _i; /* ranged test */ - if (!libinput_device_switch_has_switch(sw->libinput_device, which)) + if (libinput_device_switch_has_switch(sw->libinput_device, which) <= 0) return; touchpad = switch_init_paired_touchpad(li); @@ -295,8 +331,10 @@ START_TEST(switch_disable_touchpad_during_touch) litest_touch_move_to(touchpad, 0, 50, 50, 70, 50, 5); litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION); + litest_grab_device(sw); litest_switch_action(sw, which, LIBINPUT_SWITCH_STATE_ON); litest_assert_only_typed_events(li, LIBINPUT_EVENT_SWITCH_TOGGLE); + litest_ungrab_device(sw); litest_touch_move_to(touchpad, 0, 70, 50, 50, 50, 5); litest_touch_up(touchpad, 0); @@ -313,7 +351,7 @@ START_TEST(switch_disable_touchpad_edge_scroll) struct libinput *li = sw->libinput; enum libinput_switch which = _i; /* ranged test */ - if (!libinput_device_switch_has_switch(sw->libinput_device, which)) + if (libinput_device_switch_has_switch(sw->libinput_device, which) <= 0) return; touchpad = switch_init_paired_touchpad(li); @@ -321,8 +359,10 @@ START_TEST(switch_disable_touchpad_edge_scroll) litest_drain_events(li); + litest_grab_device(sw); litest_switch_action(sw, which, LIBINPUT_SWITCH_STATE_ON); litest_assert_only_typed_events(li, LIBINPUT_EVENT_SWITCH_TOGGLE); + litest_ungrab_device(sw); litest_touch_down(touchpad, 0, 99, 20); libinput_dispatch(li); @@ -351,7 +391,7 @@ START_TEST(switch_disable_touchpad_edge_scroll_interrupt) struct libinput_event *event; enum libinput_switch which = _i; /* ranged test */ - if (!libinput_device_switch_has_switch(sw->libinput_device, which)) + if (libinput_device_switch_has_switch(sw->libinput_device, which) <= 0) return; touchpad = switch_init_paired_touchpad(li); @@ -366,7 +406,9 @@ START_TEST(switch_disable_touchpad_edge_scroll_interrupt) libinput_dispatch(li); litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_AXIS); + litest_grab_device(sw); litest_switch_action(sw, which, LIBINPUT_SWITCH_STATE_ON); + litest_ungrab_device(sw); libinput_dispatch(li); event = libinput_get_event(li); @@ -390,7 +432,7 @@ START_TEST(switch_disable_touchpad_already_open) struct libinput *li = sw->libinput; enum libinput_switch which = _i; /* ranged test */ - if (!libinput_device_switch_has_switch(sw->libinput_device, which)) + if (libinput_device_switch_has_switch(sw->libinput_device, which) <= 0) return; touchpad = switch_init_paired_touchpad(li); @@ -424,7 +466,7 @@ START_TEST(switch_dont_resume_disabled_touchpad) struct libinput *li = sw->libinput; enum libinput_switch which = _i; /* ranged test */ - if (!libinput_device_switch_has_switch(sw->libinput_device, which)) + if (libinput_device_switch_has_switch(sw->libinput_device, which) <= 0) return; touchpad = switch_init_paired_touchpad(li); @@ -434,7 +476,9 @@ START_TEST(switch_dont_resume_disabled_touchpad) litest_drain_events(li); /* switch is on - no events */ + litest_grab_device(sw); litest_switch_action(sw, which, LIBINPUT_SWITCH_STATE_ON); + litest_ungrab_device(sw); litest_assert_only_typed_events(li, LIBINPUT_EVENT_SWITCH_TOGGLE); litest_touch_down(touchpad, 0, 50, 50); @@ -462,7 +506,7 @@ START_TEST(switch_dont_resume_disabled_touchpad_external_mouse) struct libinput *li = sw->libinput; enum libinput_switch which = _i; /* ranged test */ - if (!libinput_device_switch_has_switch(sw->libinput_device, which)) + if (libinput_device_switch_has_switch(sw->libinput_device, which) <= 0) return; touchpad = switch_init_paired_touchpad(li); @@ -478,7 +522,9 @@ START_TEST(switch_dont_resume_disabled_touchpad_external_mouse) litest_assert_empty_queue(li); /* switch is on - no events */ + litest_grab_device(sw); litest_switch_action(sw, which, LIBINPUT_SWITCH_STATE_ON); + litest_ungrab_device(sw); litest_assert_only_typed_events(li, LIBINPUT_EVENT_SWITCH_TOGGLE); litest_touch_down(touchpad, 0, 50, 50); @@ -512,6 +558,7 @@ START_TEST(lid_open_on_key) keyboard = litest_add_device(li, LITEST_KEYBOARD); + litest_grab_device(sw); for (int i = 0; i < 3; i++) { litest_switch_action(sw, LIBINPUT_SWITCH_LID, @@ -537,6 +584,7 @@ START_TEST(lid_open_on_key) LIBINPUT_SWITCH_STATE_OFF); litest_assert_empty_queue(li); } + litest_ungrab_device(sw); litest_delete_device(keyboard); } @@ -554,9 +602,11 @@ START_TEST(lid_open_on_key_touchpad_enabled) keyboard = litest_add_device(li, LITEST_KEYBOARD); touchpad = litest_add_device(li, LITEST_SYNAPTICS_I2C); + litest_grab_device(sw); litest_switch_action(sw, LIBINPUT_SWITCH_LID, LIBINPUT_SWITCH_STATE_ON); + litest_ungrab_device(sw); litest_drain_events(li); litest_event(keyboard, EV_KEY, KEY_A, 1); @@ -603,10 +653,12 @@ START_TEST(switch_suspend_with_keyboard) keyboard = litest_add_device(li, LITEST_KEYBOARD); libinput_dispatch(li); + litest_grab_device(sw); litest_switch_action(sw, which, LIBINPUT_SWITCH_STATE_ON); litest_drain_events(li); litest_switch_action(sw, which, LIBINPUT_SWITCH_STATE_OFF); litest_drain_events(li); + litest_ungrab_device(sw); litest_delete_device(keyboard); litest_drain_events(li); @@ -614,7 +666,7 @@ START_TEST(switch_suspend_with_keyboard) litest_delete_device(sw); libinput_dispatch(li); - libinput_unref(li); + litest_destroy_context(li); } END_TEST @@ -640,16 +692,21 @@ START_TEST(switch_suspend_with_touchpad) litest_drain_events(li); touchpad = litest_add_device(li, LITEST_SYNAPTICS_I2C); - litest_delete_device(touchpad); - touchpad = litest_add_device(li, LITEST_SYNAPTICS_I2C); litest_drain_events(li); + litest_grab_device(sw); + litest_switch_action(sw, which, LIBINPUT_SWITCH_STATE_ON); + litest_drain_events(li); + litest_switch_action(sw, which, LIBINPUT_SWITCH_STATE_OFF); + litest_drain_events(li); + litest_ungrab_device(sw); + litest_delete_device(sw); litest_drain_events(li); litest_delete_device(touchpad); litest_drain_events(li); - libinput_unref(li); + litest_destroy_context(li); } END_TEST @@ -657,49 +714,54 @@ START_TEST(lid_update_hw_on_key) { struct litest_device *sw = litest_current_device(); struct libinput *li = sw->libinput; - struct libinput *li2; struct litest_device *keyboard; - struct libinput_event *event; + struct libevdev *evdev; + struct input_event event; + int fd; + int rc; if (!switch_has_lid(sw)) return; keyboard = litest_add_device(li, LITEST_KEYBOARD); - /* separate context to listen to the fake hw event */ - li2 = litest_create_context(); - libinput_path_add_device(li2, - libevdev_uinput_get_devnode(sw->uinput)); - litest_drain_events(li2); - + litest_grab_device(sw); litest_switch_action(sw, LIBINPUT_SWITCH_LID, LIBINPUT_SWITCH_STATE_ON); litest_drain_events(li); + litest_ungrab_device(sw); - libinput_dispatch(li2); - event = libinput_get_event(li2); - litest_is_switch_event(event, - LIBINPUT_SWITCH_LID, - LIBINPUT_SWITCH_STATE_ON); - libinput_event_destroy(event); + /* Separate direct libevdev context to check if the HW event goes + * through */ + fd = open(libevdev_uinput_get_devnode(sw->uinput), O_RDONLY|O_NONBLOCK); + ck_assert_int_ge(fd, 0); + ck_assert_int_eq(libevdev_new_from_fd(fd, &evdev), 0); + ck_assert_int_eq(libevdev_get_event_value(evdev, EV_SW, SW_LID), 1); + /* Typing on the keyboard should trigger a lid open event */ litest_event(keyboard, EV_KEY, KEY_A, 1); litest_event(keyboard, EV_SYN, SYN_REPORT, 0); litest_event(keyboard, EV_KEY, KEY_A, 0); litest_event(keyboard, EV_SYN, SYN_REPORT, 0); litest_drain_events(li); - litest_wait_for_event(li2); - event = libinput_get_event(li2); - litest_is_switch_event(event, - LIBINPUT_SWITCH_LID, - LIBINPUT_SWITCH_STATE_OFF); - libinput_event_destroy(event); - litest_assert_empty_queue(li2); + rc = libevdev_next_event(evdev, LIBEVDEV_READ_FLAG_NORMAL, &event); + ck_assert_int_eq(rc, LIBEVDEV_READ_STATUS_SUCCESS); + ck_assert_int_eq(event.type, EV_SW); + ck_assert_int_eq(event.code, SW_LID); + ck_assert_int_eq(event.value, 0); + rc = libevdev_next_event(evdev, LIBEVDEV_READ_FLAG_NORMAL, &event); + ck_assert_int_eq(rc, LIBEVDEV_READ_STATUS_SUCCESS); + ck_assert_int_eq(event.type, EV_SYN); + ck_assert_int_eq(event.code, SYN_REPORT); + ck_assert_int_eq(event.value, 0); + rc = libevdev_next_event(evdev, LIBEVDEV_READ_FLAG_NORMAL, &event); + ck_assert_int_eq(rc, -EAGAIN); - libinput_unref(li2); litest_delete_device(keyboard); + close(fd); + libevdev_free(evdev); } END_TEST @@ -709,17 +771,22 @@ START_TEST(lid_update_hw_on_key_closed_on_init) struct libinput *li; struct litest_device *keyboard; struct libevdev *evdev = sw->evdev; - struct input_event ev; + struct input_event event; + int fd; + int rc; + litest_grab_device(sw); litest_switch_action(sw, LIBINPUT_SWITCH_LID, LIBINPUT_SWITCH_STATE_ON); + litest_ungrab_device(sw); - /* Make sure kernel state is right */ - libevdev_next_event(evdev, LIBEVDEV_READ_FLAG_FORCE_SYNC, &ev); - while (libevdev_next_event(evdev, LIBEVDEV_READ_FLAG_SYNC, &ev) >= 0) - ; - ck_assert(libevdev_get_event_value(evdev, EV_SW, SW_LID)); + /* Separate direct libevdev context to check if the HW event goes + * through */ + fd = open(libevdev_uinput_get_devnode(sw->uinput), O_RDONLY|O_NONBLOCK); + ck_assert_int_ge(fd, 0); + ck_assert_int_eq(libevdev_new_from_fd(fd, &evdev), 0); + ck_assert_int_eq(libevdev_get_event_value(evdev, EV_SW, SW_LID), 1); keyboard = litest_add_device(sw->libinput, LITEST_KEYBOARD); @@ -730,7 +797,8 @@ START_TEST(lid_update_hw_on_key_closed_on_init) libinput_path_add_device(li, libevdev_uinput_get_devnode(keyboard->uinput)); - /* don't expect a switch waiting for us */ + /* don't expect a switch waiting for us, this is run for an + * unreliable device */ while (libinput_next_event_type(li) != LIBINPUT_EVENT_NONE) { ck_assert_int_ne(libinput_next_event_type(li), LIBINPUT_EVENT_SWITCH_TOGGLE); @@ -745,13 +813,23 @@ START_TEST(lid_update_hw_on_key_closed_on_init) litest_assert_only_typed_events(li, LIBINPUT_EVENT_KEYBOARD_KEY); /* Make sure kernel state has updated */ - libevdev_next_event(evdev, LIBEVDEV_READ_FLAG_FORCE_SYNC, &ev); - while (libevdev_next_event(evdev, LIBEVDEV_READ_FLAG_SYNC, &ev) >= 0) - ; - ck_assert(!libevdev_get_event_value(evdev, EV_SW, SW_LID)); - - libinput_unref(li); + rc = libevdev_next_event(evdev, LIBEVDEV_READ_FLAG_NORMAL, &event); + ck_assert_int_eq(rc, LIBEVDEV_READ_STATUS_SUCCESS); + ck_assert_int_eq(event.type, EV_SW); + ck_assert_int_eq(event.code, SW_LID); + ck_assert_int_eq(event.value, 0); + rc = libevdev_next_event(evdev, LIBEVDEV_READ_FLAG_NORMAL, &event); + ck_assert_int_eq(rc, LIBEVDEV_READ_STATUS_SUCCESS); + ck_assert_int_eq(event.type, EV_SYN); + ck_assert_int_eq(event.code, SYN_REPORT); + ck_assert_int_eq(event.value, 0); + rc = libevdev_next_event(evdev, LIBEVDEV_READ_FLAG_NORMAL, &event); + ck_assert_int_eq(rc, -EAGAIN); + + litest_destroy_context(li); litest_delete_device(keyboard); + close(fd); + libevdev_free(evdev); } END_TEST @@ -759,9 +837,11 @@ START_TEST(lid_update_hw_on_key_multiple_keyboards) { struct litest_device *sw = litest_current_device(); struct libinput *li = sw->libinput; - struct libinput *li2; struct litest_device *keyboard1, *keyboard2; - struct libinput_event *event; + struct libevdev *evdev = sw->evdev; + struct input_event event; + int fd; + int rc; if (!switch_has_lid(sw)) return; @@ -773,41 +853,44 @@ START_TEST(lid_update_hw_on_key_multiple_keyboards) keyboard2 = litest_add_device(li, LITEST_KEYBOARD_BLADE_STEALTH); libinput_dispatch(li); - /* separate context to listen to the fake hw event */ - li2 = litest_create_context(); - libinput_path_add_device(li2, - libevdev_uinput_get_devnode(sw->uinput)); - litest_drain_events(li2); - + litest_grab_device(sw); litest_switch_action(sw, LIBINPUT_SWITCH_LID, LIBINPUT_SWITCH_STATE_ON); litest_drain_events(li); + litest_ungrab_device(sw); - libinput_dispatch(li2); - event = libinput_get_event(li2); - litest_is_switch_event(event, - LIBINPUT_SWITCH_LID, - LIBINPUT_SWITCH_STATE_ON); - libinput_event_destroy(event); + /* Separate direct libevdev context to check if the HW event goes + * through */ + fd = open(libevdev_uinput_get_devnode(sw->uinput), O_RDONLY|O_NONBLOCK); + ck_assert_int_ge(fd, 0); + ck_assert_int_eq(libevdev_new_from_fd(fd, &evdev), 0); + ck_assert_int_eq(libevdev_get_event_value(evdev, EV_SW, SW_LID), 1); + /* Typing on the second keyboard should trigger a lid open event */ litest_event(keyboard2, EV_KEY, KEY_A, 1); litest_event(keyboard2, EV_SYN, SYN_REPORT, 0); litest_event(keyboard2, EV_KEY, KEY_A, 0); litest_event(keyboard2, EV_SYN, SYN_REPORT, 0); litest_drain_events(li); - litest_wait_for_event(li2); - event = libinput_get_event(li2); - litest_is_switch_event(event, - LIBINPUT_SWITCH_LID, - LIBINPUT_SWITCH_STATE_OFF); - libinput_event_destroy(event); - litest_assert_empty_queue(li2); + rc = libevdev_next_event(evdev, LIBEVDEV_READ_FLAG_NORMAL, &event); + ck_assert_int_eq(rc, LIBEVDEV_READ_STATUS_SUCCESS); + ck_assert_int_eq(event.type, EV_SW); + ck_assert_int_eq(event.code, SW_LID); + ck_assert_int_eq(event.value, 0); + rc = libevdev_next_event(evdev, LIBEVDEV_READ_FLAG_NORMAL, &event); + ck_assert_int_eq(rc, LIBEVDEV_READ_STATUS_SUCCESS); + ck_assert_int_eq(event.type, EV_SYN); + ck_assert_int_eq(event.code, SYN_REPORT); + ck_assert_int_eq(event.value, 0); + rc = libevdev_next_event(evdev, LIBEVDEV_READ_FLAG_NORMAL, &event); + ck_assert_int_eq(rc, -EAGAIN); - libinput_unref(li2); litest_delete_device(keyboard1); litest_delete_device(keyboard2); + close(fd); + libevdev_free(evdev); } END_TEST @@ -836,6 +919,7 @@ START_TEST(tablet_mode_disable_touchpad_on_init) if (!switch_has_tablet_mode(sw)) return; + litest_grab_device(sw); litest_switch_action(sw, LIBINPUT_SWITCH_TABLET_MODE, LIBINPUT_SWITCH_STATE_ON); @@ -860,6 +944,7 @@ START_TEST(tablet_mode_disable_touchpad_on_init) litest_touch_move_to(touchpad, 0, 50, 50, 70, 50, 10); litest_touch_up(touchpad, 0); litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION); + litest_ungrab_device(sw); litest_delete_device(touchpad); } @@ -1074,9 +1159,14 @@ START_TEST(tablet_mode_disable_keyboard_on_resume) litest_drain_events(li); libinput_suspend(li); + /* We cannot grab this device because libinput doesn't have an open + * fd to this device, we need an independent grab. + */ + libevdev_grab(sw->evdev, LIBEVDEV_GRAB); litest_switch_action(sw, LIBINPUT_SWITCH_TABLET_MODE, LIBINPUT_SWITCH_STATE_ON); + libevdev_grab(sw->evdev, LIBEVDEV_UNGRAB); litest_drain_events(li); libinput_resume(li); @@ -1107,9 +1197,11 @@ START_TEST(tablet_mode_disable_keyboard_on_resume) litest_keyboard_key(keyboard, KEY_A, false); litest_assert_empty_queue(li); + litest_grab_device(sw); litest_switch_action(sw, LIBINPUT_SWITCH_TABLET_MODE, LIBINPUT_SWITCH_STATE_OFF); + litest_ungrab_device(sw); litest_assert_only_typed_events(li, LIBINPUT_EVENT_SWITCH_TOGGLE); litest_keyboard_key(keyboard, KEY_A, true); @@ -1130,10 +1222,12 @@ START_TEST(tablet_mode_enable_keyboard_on_resume) return; keyboard = litest_add_device(li, LITEST_KEYBOARD); + litest_grab_device(sw); litest_switch_action(sw, LIBINPUT_SWITCH_TABLET_MODE, LIBINPUT_SWITCH_STATE_ON); litest_drain_events(li); + litest_ungrab_device(sw); libinput_suspend(li); litest_drain_events(li); @@ -1180,6 +1274,7 @@ START_TEST(tablet_mode_disable_trackpoint) litest_event(trackpoint, EV_SYN, SYN_REPORT, 0); litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION); + litest_grab_device(sw); litest_switch_action(sw, LIBINPUT_SWITCH_TABLET_MODE, LIBINPUT_SWITCH_STATE_ON); @@ -1201,6 +1296,7 @@ START_TEST(tablet_mode_disable_trackpoint) litest_event(trackpoint, EV_REL, REL_Y, -1); litest_event(trackpoint, EV_SYN, SYN_REPORT, 0); litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION); + litest_ungrab_device(sw); litest_delete_device(trackpoint); } @@ -1215,6 +1311,7 @@ START_TEST(tablet_mode_disable_trackpoint_on_init) if (!switch_has_tablet_mode(sw)) return; + litest_grab_device(sw); litest_switch_action(sw, LIBINPUT_SWITCH_TABLET_MODE, LIBINPUT_SWITCH_STATE_ON); @@ -1240,6 +1337,7 @@ START_TEST(tablet_mode_disable_trackpoint_on_init) litest_event(trackpoint, EV_REL, REL_Y, -1); litest_event(trackpoint, EV_SYN, SYN_REPORT, 0); litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION); + litest_ungrab_device(sw); litest_delete_device(trackpoint); } @@ -1255,11 +1353,13 @@ START_TEST(dock_toggle) litest_drain_events(li); + litest_grab_device(sw); litest_event(sw, EV_SW, SW_DOCK, 1); libinput_dispatch(li); litest_event(sw, EV_SW, SW_DOCK, 0); libinput_dispatch(li); + litest_ungrab_device(sw); litest_assert_empty_queue(li); } diff --git a/test/test-tablet.c b/test/test-tablet.c index f4915bc..a3060f1 100644 --- a/test/test-tablet.c +++ b/test/test-tablet.c @@ -202,7 +202,7 @@ START_TEST(button_up_on_delete) litest_assert_tablet_proximity_event(li, LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_OUT); libevdev_free(evdev); - libinput_unref(li); + litest_destroy_context(li); } END_TEST @@ -923,7 +923,7 @@ START_TEST(tip_up_on_delete) LIBINPUT_TABLET_TOOL_TIP_UP); libinput_event_destroy(event); - libinput_unref(li); + litest_destroy_context(li); } END_TEST @@ -1098,9 +1098,6 @@ START_TEST(proximity_out_clear_buttons) litest_tablet_proximity_out(dev); libinput_dispatch(li); - litest_timeout_tablet_proxout(); - libinput_dispatch(li); - event = libinput_get_event(li); ck_assert_notnull(event); do { @@ -1570,31 +1567,121 @@ START_TEST(proximity_out_not_during_contact) } END_TEST -START_TEST(proximity_out_no_timeout) +START_TEST(proximity_out_not_during_buttonpress) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct axis_replacement axes[] = { + { ABS_DISTANCE, 10 }, + { ABS_PRESSURE, 0 }, + { -1, -1 } + }; + + litest_tablet_proximity_in(dev, 10, 10, axes); + litest_tablet_motion(dev, 12, 12, axes); + litest_drain_events(li); + + litest_event(dev, EV_KEY, BTN_STYLUS, 1); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + libinput_dispatch(li); + + litest_assert_only_typed_events(li, LIBINPUT_EVENT_TABLET_TOOL_BUTTON); + + litest_timeout_tablet_proxout(); + libinput_dispatch(li); + + /* No forced proxout yet */ + litest_assert_empty_queue(li); + + litest_event(dev, EV_KEY, BTN_STYLUS, 0); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + libinput_dispatch(li); + + litest_assert_only_typed_events(li, LIBINPUT_EVENT_TABLET_TOOL_BUTTON); + + litest_timeout_tablet_proxout(); + libinput_dispatch(li); + + /* The forced prox out */ + litest_assert_tablet_proximity_event(li, + LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_OUT); + + litest_tablet_proximity_out(dev); + litest_assert_empty_queue(li); +} +END_TEST + +START_TEST(proximity_out_disables_forced) { struct litest_device *dev = litest_current_device(); struct libinput *li = dev->libinput; struct axis_replacement axes[] = { + { ABS_DISTANCE, 10 }, { ABS_PRESSURE, 0 }, { -1, -1 } }; + /* A correct proximity out sequence from the device should disable + the forced proximity out */ + litest_tablet_proximity_in(dev, 10, 10, axes); + litest_tablet_proximity_out(dev); litest_drain_events(li); + /* expect no timeout-based prox out */ litest_tablet_proximity_in(dev, 10, 10, axes); + litest_drain_events(li); + + litest_timeout_tablet_proxout(); + libinput_dispatch(li); + + litest_assert_empty_queue(li); + litest_tablet_proximity_out(dev); litest_assert_tablet_proximity_event(li, - LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN); - litest_tablet_motion(dev, 12, 12, axes); + LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_OUT); + libinput_dispatch(li); +} +END_TEST + +START_TEST(proximity_out_disables_forced_after_forced) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct axis_replacement axes[] = { + { ABS_DISTANCE, 10 }, + { ABS_PRESSURE, 0 }, + { -1, -1 } + }; + + /* A correct proximity out sequence from the device should disable + the forced proximity out, even when we had a forced prox-out */ + litest_tablet_proximity_in(dev, 10, 10, axes); litest_drain_events(li); + /* timeout-based forced prox out */ litest_timeout_tablet_proxout(); + libinput_dispatch(li); + litest_assert_tablet_proximity_event(li, + LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_OUT); litest_assert_empty_queue(li); + /* now send the real prox out (we're already in proximity out) and + * that should disable the proxout quirk */ + litest_tablet_proximity_out(dev); + libinput_dispatch(li); + litest_assert_empty_queue(li); + + /* same again, but this time we expect no timeout-based prox out */ + litest_tablet_proximity_in(dev, 10, 10, axes); + litest_drain_events(li); + + litest_timeout_tablet_proxout(); + libinput_dispatch(li); + + litest_assert_empty_queue(li); litest_tablet_proximity_out(dev); - /* The forced prox out */ litest_assert_tablet_proximity_event(li, LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_OUT); - litest_assert_empty_queue(li); + libinput_dispatch(li); } END_TEST @@ -1611,7 +1698,7 @@ START_TEST(proximity_out_on_delete) litest_assert_tablet_proximity_event(li, LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_OUT); - libinput_unref(li); + litest_destroy_context(li); } END_TEST @@ -2488,7 +2575,7 @@ START_TEST(tools_with_serials) litest_delete_device(dev[0]); litest_delete_device(dev[1]); - libinput_unref(li); + litest_destroy_context(li); } END_TEST @@ -2532,7 +2619,7 @@ START_TEST(tools_without_serials) litest_delete_device(dev[0]); litest_delete_device(dev[1]); - libinput_unref(li); + litest_destroy_context(li); } END_TEST @@ -2681,7 +2768,7 @@ START_TEST(tool_capabilities) litest_delete_device(bamboo); litest_delete_device(intuos); - libinput_unref(li); + litest_destroy_context(li); } END_TEST @@ -2814,10 +2901,8 @@ START_TEST(tool_in_prox_before_start) libinput_event_destroy(event); litest_assert_empty_queue(li); - litest_event(dev, EV_KEY, BTN_STYLUS, 1); - litest_event(dev, EV_SYN, SYN_REPORT, 0); - litest_event(dev, EV_KEY, BTN_STYLUS, 1); - litest_event(dev, EV_SYN, SYN_REPORT, 0); + litest_button_click(dev, BTN_STYLUS, true); + litest_button_click(dev, BTN_STYLUS, false); litest_assert_only_typed_events(li, LIBINPUT_EVENT_TABLET_TOOL_BUTTON); litest_tablet_proximity_out(dev); libinput_dispatch(li); @@ -2825,52 +2910,11 @@ START_TEST(tool_in_prox_before_start) litest_timeout_tablet_proxout(); libinput_dispatch(li); - litest_wait_for_event_of_type(li, - LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY, - -1); - libinput_unref(li); -} -END_TEST - -static void tool_switch_warning(struct libinput *libinput, - enum libinput_log_priority priority, - const char *format, - va_list args) -{ - int *warning_triggered = (int*)libinput_get_user_data(libinput); - - if (priority == LIBINPUT_LOG_PRIORITY_ERROR && - strstr(format, "Multiple tools active simultaneously")) - (*warning_triggered)++; -} - - -START_TEST(tool_direct_switch_warning) -{ - struct litest_device *dev = litest_current_device(); - struct libinput *li = dev->libinput; - struct axis_replacement axes[] = { - { ABS_DISTANCE, 10 }, - { ABS_PRESSURE, 0 }, - { -1, -1 } - }; - int warning_triggered = 0; - - if (!libevdev_has_event_code(dev->evdev, EV_KEY, BTN_TOOL_RUBBER)) - return; - - libinput_set_user_data(li, &warning_triggered); - libinput_log_set_handler(li, tool_switch_warning); - - litest_tablet_proximity_in(dev, 10, 10, axes); - litest_drain_events(li); - - litest_event(dev, EV_KEY, BTN_TOOL_RUBBER, 1); - litest_event(dev, EV_SYN, SYN_REPORT, 0); - libinput_dispatch(li); + event = libinput_get_event(li); + litest_is_tablet_event(event, LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + libinput_event_destroy(event); - ck_assert_int_eq(warning_triggered, 1); - litest_restore_log_handler(li); + litest_destroy_context(li); } END_TEST @@ -2902,12 +2946,28 @@ START_TEST(tool_direct_switch_skip_tool_update) libinput_tablet_tool_ref(tool); libinput_event_destroy(event); - /* Direct tool switch after proximity in is ignored */ - litest_disable_log_handler(li); litest_event(dev, EV_KEY, BTN_TOOL_RUBBER, 1); litest_event(dev, EV_SYN, SYN_REPORT, 0); - litest_assert_empty_queue(li); - litest_restore_log_handler(li); + libinput_dispatch(li); + + event = libinput_get_event(li); + tev = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + ck_assert_int_eq(libinput_event_tablet_tool_get_proximity_state(tev), + LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_OUT); + ck_assert_ptr_eq(libinput_event_tablet_tool_get_tool(tev), tool); + libinput_event_destroy(event); + + event = libinput_get_event(li); + tev = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + ck_assert_int_eq(libinput_event_tablet_tool_get_proximity_state(tev), + LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN); + ck_assert_ptr_ne(libinput_event_tablet_tool_get_tool(tev), tool); + libinput_tablet_tool_unref(tool); + tool = libinput_event_tablet_tool_get_tool(tev); + libinput_tablet_tool_ref(tool); + libinput_event_destroy(event); litest_tablet_motion(dev, 20, 30, axes); libinput_dispatch(li); @@ -2919,18 +2979,36 @@ START_TEST(tool_direct_switch_skip_tool_update) tool); libinput_event_destroy(event); - /* Direct tool switch during sequence in is ignored */ - litest_disable_log_handler(li); litest_event(dev, EV_KEY, BTN_TOOL_RUBBER, 0); litest_event(dev, EV_SYN, SYN_REPORT, 0); - litest_assert_empty_queue(li); + libinput_dispatch(li); + + event = libinput_get_event(li); + tev = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + ck_assert_int_eq(libinput_event_tablet_tool_get_proximity_state(tev), + LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_OUT); + ck_assert_ptr_eq(libinput_event_tablet_tool_get_tool(tev), + tool); + libinput_event_destroy(event); litest_push_event_frame(dev); litest_event(dev, EV_KEY, BTN_TOOL_RUBBER, 1); litest_tablet_motion(dev, 30, 40, axes); litest_pop_event_frame(dev); libinput_dispatch(li); - litest_restore_log_handler(li); + + event = libinput_get_event(li); + tev = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + ck_assert_int_eq(libinput_event_tablet_tool_get_proximity_state(tev), + LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN); + ck_assert_ptr_eq(libinput_event_tablet_tool_get_tool(tev), + tool); + libinput_event_destroy(event); + + litest_tablet_motion(dev, 40, 30, axes); + libinput_dispatch(li); event = libinput_get_event(li); tev = litest_is_tablet_event(event, @@ -2939,7 +3017,10 @@ START_TEST(tool_direct_switch_skip_tool_update) tool); libinput_event_destroy(event); + litest_push_event_frame(dev); + litest_event(dev, EV_KEY, BTN_TOOL_RUBBER, 0); litest_tablet_proximity_out(dev); + litest_pop_event_frame(dev); libinput_dispatch(li); litest_timeout_tablet_proxout(); libinput_dispatch(li); @@ -2959,6 +3040,84 @@ START_TEST(tool_direct_switch_skip_tool_update) } END_TEST +START_TEST(tool_direct_switch_with_forced_proxout) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + struct axis_replacement axes[] = { + { ABS_DISTANCE, 10 }, + { ABS_PRESSURE, 0 }, + { -1, -1 } + }; + + if (!libevdev_has_event_code(dev->evdev, EV_KEY, BTN_TOOL_RUBBER)) + return; + + litest_drain_events(li); + + /* This is a *very* specific event sequence to trigger a bug: + - pen proximity in + - pen proximity forced out + - eraser proximity in without axis movement + - eraser axis move + */ + + /* pen prox in */ + litest_tablet_proximity_in(dev, 10, 10, axes); + libinput_dispatch(li); + event = libinput_get_event(li); + litest_is_proximity_event(event, + LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN); + libinput_event_destroy(event); + + /* pen motion */ + litest_tablet_motion(dev, 20, 30, axes); + libinput_dispatch(li); + + event = libinput_get_event(li); + litest_is_tablet_event(event, LIBINPUT_EVENT_TABLET_TOOL_AXIS); + libinput_event_destroy(event); + + /* pen forced prox out */ + litest_timeout_tablet_proxout(); + libinput_dispatch(li); + + /* actual prox out for tablets that don't do forced prox out */ + litest_tablet_proximity_out(dev); + libinput_dispatch(li); + + event = libinput_get_event(li); + litest_is_proximity_event(event, + LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_OUT); + libinput_event_destroy(event); + + /* eraser prox in without axes */ + litest_event(dev, EV_KEY, BTN_TOOL_RUBBER, 1); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + libinput_dispatch(li); + event = libinput_get_event(li); + litest_is_proximity_event(event, + LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN); + libinput_event_destroy(event); + + /* eraser motion */ + litest_tablet_motion(dev, 30, 40, axes); + litest_tablet_motion(dev, 40, 50, axes); + libinput_dispatch(li); + + event = libinput_get_event(li); + litest_is_tablet_event(event, LIBINPUT_EVENT_TABLET_TOOL_AXIS); + libinput_event_destroy(event); + + event = libinput_get_event(li); + litest_is_tablet_event(event, LIBINPUT_EVENT_TABLET_TOOL_AXIS); + libinput_event_destroy(event); + + litest_assert_empty_queue(li); +} +END_TEST + START_TEST(mouse_tool) { struct litest_device *dev = litest_current_device(); @@ -3914,9 +4073,11 @@ START_TEST(tablet_pressure_offset_exceed_threshold) }; double pressure; int warning_triggered = 0; + void *old_user_data; litest_drain_events(li); + old_user_data = libinput_get_user_data(li); libinput_set_user_data(li, &warning_triggered); libinput_log_set_handler(li, pressure_threshold_warning); @@ -3931,6 +4092,8 @@ START_TEST(tablet_pressure_offset_exceed_threshold) ck_assert_int_eq(warning_triggered, 1); litest_restore_log_handler(li); + + libinput_set_user_data(li, old_user_data); } END_TEST @@ -4663,6 +4826,10 @@ START_TEST(touch_arbitration_outside_rect) x = 20; y = 45; + /* disable prox-out timer quirk */ + litest_tablet_proximity_in(dev, x, y - 1, axes); + litest_tablet_proximity_out(dev); + litest_tablet_proximity_in(dev, x, y - 1, axes); litest_drain_events(li); @@ -4676,25 +4843,16 @@ START_TEST(touch_arbitration_outside_rect) litest_touch_sequence(finger, 0, x - 10, y + 2, x - 10, y + 20, 3); libinput_dispatch(li); litest_assert_touch_sequence(li); - /* tablet event so we don't time out for proximity */ - litest_tablet_motion(dev, x, y - 0.1, axes); - litest_drain_events(li); /* above rect */ litest_touch_sequence(finger, 0, x + 2, y - 35, x + 20, y - 10, 3); libinput_dispatch(li); litest_assert_touch_sequence(li); - /* tablet event so we don't time out for proximity */ - litest_tablet_motion(dev, x, y + 0.1, axes); - litest_drain_events(li); /* right of rect */ litest_touch_sequence(finger, 0, x + 80, y + 2, x + 20, y + 10, 3); libinput_dispatch(li); litest_assert_touch_sequence(li); - /* tablet event so we don't time out for proximity */ - litest_tablet_motion(dev, x, y - 0.1, axes); - litest_drain_events(li); #if 0 /* This *should* work but the Cintiq test devices is <200mm @@ -4777,6 +4935,11 @@ START_TEST(touch_arbitration_stop_touch) is_touchpad = !libevdev_has_property(finger->evdev, INPUT_PROP_DIRECT); + /* disable prox-out timer quirk */ + litest_tablet_proximity_in(dev, 30, 30, axes); + litest_tablet_proximity_out(dev); + litest_drain_events(li); + litest_touch_down(finger, 0, 30, 30); litest_touch_move_to(finger, 0, 30, 30, 80, 80, 10); @@ -5174,12 +5337,6 @@ verify_left_handed_touch_motion(struct litest_device *finger, libinput_dispatch(li); event = libinput_get_event(li); - p = litest_is_motion_event(event); - x = libinput_event_pointer_get_dx(p); - y = libinput_event_pointer_get_dy(p); - libinput_event_destroy(event); - - event = libinput_get_event(li); ck_assert_notnull(event); while (event) { @@ -5220,6 +5377,7 @@ verify_left_handed_touch_sequence(struct litest_device *finger, START_TEST(tablet_rotation_left_handed) { +#if HAVE_LIBWACOM struct litest_device *tablet = litest_current_device(); enum litest_device_type other; struct litest_device *finger; @@ -5262,11 +5420,13 @@ START_TEST(tablet_rotation_left_handed) out: litest_delete_device(finger); +#endif } END_TEST START_TEST(tablet_rotation_left_handed_configuration) { +#if HAVE_LIBWACOM struct litest_device *tablet = litest_current_device(); enum litest_device_type other; struct litest_device *finger; @@ -5314,11 +5474,13 @@ START_TEST(tablet_rotation_left_handed_configuration) out: litest_delete_device(finger); +#endif } END_TEST START_TEST(tablet_rotation_left_handed_while_in_prox) { +#if HAVE_LIBWACOM struct litest_device *tablet = litest_current_device(); enum litest_device_type other; struct litest_device *finger; @@ -5405,11 +5567,13 @@ START_TEST(tablet_rotation_left_handed_while_in_prox) out: litest_delete_device(finger); +#endif } END_TEST START_TEST(tablet_rotation_left_handed_while_touch_down) { +#if HAVE_LIBWACOM struct litest_device *tablet = litest_current_device(); enum litest_device_type other; struct litest_device *finger; @@ -5471,11 +5635,13 @@ START_TEST(tablet_rotation_left_handed_while_touch_down) out: litest_delete_device(finger); +#endif } END_TEST START_TEST(tablet_rotation_left_handed_add_touchpad) { +#if HAVE_LIBWACOM struct litest_device *tablet = litest_current_device(); enum litest_device_type other; struct litest_device *finger; @@ -5523,11 +5689,13 @@ START_TEST(tablet_rotation_left_handed_add_touchpad) out: litest_delete_device(finger); +#endif } END_TEST START_TEST(tablet_rotation_left_handed_add_tablet) { +#if HAVE_LIBWACOM struct litest_device *finger = litest_current_device(); enum litest_device_type other; struct litest_device *tablet; @@ -5573,8 +5741,10 @@ START_TEST(tablet_rotation_left_handed_add_tablet) verify_left_handed_tablet_sequence(tablet, li, enabled_to); litest_delete_device(tablet); +#endif } END_TEST + START_TEST(huion_static_btn_tool_pen) { struct litest_device *dev = litest_current_device(); @@ -5721,17 +5891,10 @@ START_TEST(huion_static_btn_tool_pen_disable_quirk_on_prox_out) LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_OUT); } - litest_push_event_frame(dev); - litest_tablet_proximity_out(dev); - litest_event(dev, EV_KEY, BTN_TOOL_PEN, 0); - litest_event(dev, EV_SYN, SYN_REPORT, 0); - litest_pop_event_frame(dev); - litest_tablet_proximity_in(dev, 50, 50, NULL); libinput_dispatch(li); litest_assert_tablet_proximity_event(li, LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN); - libinput_dispatch(li); for (i = 0; i < 10; i++) { litest_tablet_motion(dev, 50 + i, 50 + i, NULL); @@ -5741,6 +5904,12 @@ START_TEST(huion_static_btn_tool_pen_disable_quirk_on_prox_out) litest_assert_only_typed_events(li, LIBINPUT_EVENT_TABLET_TOOL_AXIS); + libinput_dispatch(li); + litest_timeout_tablet_proxout(); + libinput_dispatch(li); + + litest_assert_empty_queue(li); + litest_push_event_frame(dev); litest_tablet_proximity_out(dev); litest_event(dev, EV_KEY, BTN_TOOL_PEN, 0); @@ -5766,8 +5935,8 @@ TEST_COLLECTION(tablet) litest_add_no_device("tablet:tool", tool_capabilities); litest_add("tablet:tool", tool_type, LITEST_TABLET, LITEST_ANY); litest_add("tablet:tool", tool_in_prox_before_start, LITEST_TABLET, LITEST_TOTEM); - litest_add("tablet:tool", tool_direct_switch_warning, LITEST_TABLET, LITEST_ANY); litest_add("tablet:tool", tool_direct_switch_skip_tool_update, LITEST_TABLET, LITEST_ANY); + litest_add("tablet:tool", tool_direct_switch_with_forced_proxout, LITEST_TABLET, LITEST_ANY); /* Tablets hold back the proximity until the first event from the * kernel, the totem sends it immediately */ @@ -5780,7 +5949,7 @@ TEST_COLLECTION(tablet) litest_add_no_device("tablet:tool_serial", tools_with_serials); litest_add_no_device("tablet:tool_serial", tools_without_serials); litest_add_for_device("tablet:tool_serial", tool_delayed_serial, LITEST_WACOM_HID4800_PEN); - litest_add("tablet:proximity", proximity_out_clear_buttons, LITEST_TABLET, LITEST_ANY); + litest_add("tablet:proximity", proximity_out_clear_buttons, LITEST_TABLET, LITEST_FORCED_PROXOUT); litest_add("tablet:proximity", proximity_in_out, LITEST_TABLET, LITEST_ANY); litest_add("tablet:proximity", proximity_in_button_down, LITEST_TABLET, LITEST_ANY); litest_add("tablet:proximity", proximity_out_button_up, LITEST_TABLET, LITEST_ANY); @@ -5793,8 +5962,9 @@ TEST_COLLECTION(tablet) litest_add("tablet:proximity", proximity_range_button_release, LITEST_TABLET | LITEST_DISTANCE | LITEST_TOOL_MOUSE, LITEST_ANY); litest_add("tablet:proximity", proximity_out_slow_event, LITEST_TABLET | LITEST_DISTANCE, LITEST_ANY); litest_add("tablet:proximity", proximity_out_not_during_contact, LITEST_TABLET | LITEST_DISTANCE, LITEST_ANY); - litest_add_for_device("tablet:proximity", proximity_out_no_timeout, LITEST_WACOM_ISDV4_4200_PEN); - + litest_add("tablet:proximity", proximity_out_not_during_buttonpress, LITEST_TABLET | LITEST_DISTANCE, LITEST_ANY); + litest_add("tablet:proximity", proximity_out_disables_forced, LITEST_TABLET, LITEST_FORCED_PROXOUT|LITEST_TOTEM); + litest_add("tablet:proximity", proximity_out_disables_forced_after_forced, LITEST_TABLET, LITEST_FORCED_PROXOUT|LITEST_TOTEM); litest_add_no_device("tablet:proximity", proximity_out_on_delete); litest_add("tablet:button", button_down_up, LITEST_TABLET, LITEST_ANY); litest_add("tablet:button", button_seat_count, LITEST_TABLET, LITEST_ANY); diff --git a/test/test-totem.c b/test/test-totem.c index bafa8d2..d10ad32 100644 --- a/test/test-totem.c +++ b/test/test-totem.c @@ -193,7 +193,7 @@ START_TEST(totem_proximity_in_on_init) litest_assert_empty_queue(li); - libinput_unref(li); + litest_destroy_context(li); } END_TEST @@ -231,7 +231,7 @@ START_TEST(totem_proximity_out_on_suspend) libinput_event_destroy(event); litest_assert_only_typed_events(li, LIBINPUT_EVENT_DEVICE_REMOVED); - libinput_unref(li); + litest_destroy_context(li); } END_TEST @@ -453,7 +453,7 @@ START_TEST(totem_button_down_on_init) libinput_dispatch(li); litest_assert_tablet_button_event(li, BTN_0, LIBINPUT_BUTTON_STATE_RELEASED); - libinput_unref(li); + litest_destroy_context(li); } END_TEST @@ -480,7 +480,7 @@ START_TEST(totem_button_up_on_delete) litest_assert_tablet_proximity_event(li, LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_OUT); libevdev_free(evdev); - libinput_unref(li); + litest_destroy_context(li); } END_TEST diff --git a/test/test-touch.c b/test/test-touch.c index 81accf9..72dca0d 100644 --- a/test/test-touch.c +++ b/test/test-touch.c @@ -871,12 +871,14 @@ START_TEST(touch_initial_state) libinput_event_destroy(ev1); libinput_event_destroy(ev2); + ev1 = NULL; + ev2 = NULL; } libinput_event_destroy(ev1); libinput_event_destroy(ev2); - libinput_unref(libinput2); + litest_destroy_context(libinput2); } END_TEST @@ -1001,7 +1003,7 @@ START_TEST(touch_release_on_unplug) litest_assert_event_type(ev, LIBINPUT_EVENT_DEVICE_REMOVED); libinput_event_destroy(ev); - libinput_unref(li); + litest_destroy_context(li); } END_TEST diff --git a/test/test-touchpad-buttons.c b/test/test-touchpad-buttons.c index 907d12b..d6a5ceb 100644 --- a/test/test-touchpad-buttons.c +++ b/test/test-touchpad-buttons.c @@ -219,7 +219,7 @@ START_TEST(touchpad_3fg_clickfinger) struct litest_device *dev = litest_current_device(); struct libinput *li = dev->libinput; - if (libevdev_get_num_slots(dev->evdev) < 3) + if (litest_slot_count(dev) < 3) return; litest_enable_clickfinger(dev); @@ -253,7 +253,7 @@ START_TEST(touchpad_3fg_clickfinger_btntool) struct litest_device *dev = litest_current_device(); struct libinput *li = dev->libinput; - if (libevdev_get_num_slots(dev->evdev) >= 3 || + if (litest_slot_count(dev) >= 3 || !libevdev_has_event_code(dev->evdev, EV_KEY, BTN_TOOL_TRIPLETAP)) return; @@ -293,7 +293,7 @@ START_TEST(touchpad_4fg_clickfinger) struct libinput *li = dev->libinput; struct libinput_event *event; - if (libevdev_get_num_slots(dev->evdev) < 4) + if (litest_slot_count(dev) < 4) return; litest_enable_clickfinger(dev); @@ -337,7 +337,7 @@ START_TEST(touchpad_4fg_clickfinger_btntool_2slots) struct libinput *li = dev->libinput; struct libinput_event *event; - if (libevdev_get_num_slots(dev->evdev) >= 3 || + if (litest_slot_count(dev) >= 3 || !libevdev_has_event_code(dev->evdev, EV_KEY, BTN_TOOL_QUADTAP)) return; @@ -382,8 +382,7 @@ START_TEST(touchpad_4fg_clickfinger_btntool_3slots) struct libinput *li = dev->libinput; struct libinput_event *event; - if (libevdev_get_num_slots(dev->evdev) >= 4 || - libevdev_get_num_slots(dev->evdev) < 3 || + if (litest_slot_count(dev) != 3 || !libevdev_has_event_code(dev->evdev, EV_KEY, BTN_TOOL_TRIPLETAP)) return; @@ -490,7 +489,7 @@ START_TEST(touchpad_3fg_clickfinger_distance) struct litest_device *dev = litest_current_device(); struct libinput *li = dev->libinput; - if (libevdev_get_num_slots(dev->evdev) < 3) + if (litest_slot_count(dev) < 3) return; litest_enable_clickfinger(dev); @@ -523,7 +522,7 @@ START_TEST(touchpad_3fg_clickfinger_distance_btntool) struct litest_device *dev = litest_current_device(); struct libinput *li = dev->libinput; - if (libevdev_get_num_slots(dev->evdev) > 2) + if (litest_slot_count(dev) > 2) return; litest_enable_clickfinger(dev); @@ -964,7 +963,7 @@ START_TEST(touchpad_clickfinger_click_drag) struct libinput *li = dev->libinput; int nfingers = _i; /* ranged test */ unsigned int button; - int nslots = libevdev_get_num_slots(dev->evdev); + int nslots = litest_slot_count(dev); litest_enable_clickfinger(dev); litest_drain_events(li); @@ -1037,7 +1036,6 @@ START_TEST(touchpad_clickfinger_click_drag) } else { litest_event(dev, EV_KEY, BTN_TOOL_DOUBLETAP, 0); } - button = BTN_MIDDLE; } litest_touch_up(dev, 0); diff --git a/test/test-touchpad-tap.c b/test/test-touchpad-tap.c index 0862908..74e03cc 100644 --- a/test/test-touchpad-tap.c +++ b/test/test-touchpad-tap.c @@ -936,7 +936,7 @@ START_TEST(touchpad_2fg_tap_n_drag_3fg_btntool) struct litest_device *dev = litest_current_device(); struct libinput *li = dev->libinput; - if (libevdev_get_abs_maximum(dev->evdev, ABS_MT_SLOT) > 2 || + if (litest_slot_count(dev) > 2 || !libevdev_has_event_code(dev->evdev, EV_KEY, BTN_TOOL_TRIPLETAP)) return; @@ -981,8 +981,7 @@ START_TEST(touchpad_2fg_tap_n_drag_3fg) struct litest_device *dev = litest_current_device(); struct libinput *li = dev->libinput; - if (libevdev_get_abs_maximum(dev->evdev, - ABS_MT_SLOT) <= 2) + if (litest_slot_count(dev) < 3) return; litest_enable_tap(dev->libinput_device); @@ -1523,8 +1522,7 @@ START_TEST(touchpad_3fg_tap) unsigned int button = 0; int i; - if (libevdev_get_abs_maximum(dev->evdev, - ABS_MT_SLOT) <= 2) + if (litest_slot_count(dev) < 3) return; litest_enable_tap(dev->libinput_device); @@ -1586,7 +1584,7 @@ START_TEST(touchpad_3fg_tap_tap_again) struct libinput *li = dev->libinput; int i; - if (libevdev_get_abs_maximum(dev->evdev, ABS_MT_SLOT) <= 2) + if (litest_slot_count(dev) < 3) return; litest_enable_tap(dev->libinput_device); @@ -1637,8 +1635,7 @@ 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) + if (litest_slot_count(dev) < 3) return; litest_enable_tap(dev->libinput_device); @@ -1676,7 +1673,7 @@ START_TEST(touchpad_3fg_tap_pressure_btntool) struct litest_device *dev = litest_current_device(); struct libinput *li = dev->libinput; - if (libevdev_get_abs_maximum(dev->evdev, ABS_MT_SLOT) >= 2 || + if (litest_slot_count(dev) >= 3 || !libevdev_has_event_code(dev->evdev, EV_KEY, BTN_TOOL_TRIPLETAP)) return; @@ -1737,7 +1734,7 @@ START_TEST(touchpad_3fg_tap_hover_btntool) struct litest_device *dev = litest_current_device(); struct libinput *li = dev->libinput; - if (libevdev_get_abs_maximum(dev->evdev, ABS_MT_SLOT) >= 2 || + if (litest_slot_count(dev) >= 3 || !libevdev_has_event_code(dev->evdev, EV_KEY, BTN_TOOL_TRIPLETAP)) return; @@ -1791,7 +1788,7 @@ START_TEST(touchpad_3fg_tap_btntool) enum libinput_config_tap_button_map map = _i; /* ranged test */ unsigned int button = 0; - if (libevdev_get_abs_maximum(dev->evdev, ABS_MT_SLOT) > 2 || + if (litest_slot_count(dev) >= 3 || !libevdev_has_event_code(dev->evdev, EV_KEY, BTN_TOOL_TRIPLETAP)) return; @@ -1841,7 +1838,7 @@ START_TEST(touchpad_3fg_tap_btntool_inverted) enum libinput_config_tap_button_map map = _i; /* ranged test */ unsigned int button = 0; - if (libevdev_get_abs_maximum(dev->evdev, ABS_MT_SLOT) > 2 || + if (litest_slot_count(dev) > 3 || !libevdev_has_event_code(dev->evdev, EV_KEY, BTN_TOOL_TRIPLETAP)) return; @@ -1891,7 +1888,7 @@ START_TEST(touchpad_3fg_tap_btntool_pointerjump) enum libinput_config_tap_button_map map = _i; /* ranged test */ unsigned int button = 0; - if (libevdev_get_abs_maximum(dev->evdev, ABS_MT_SLOT) > 2 || + if (litest_slot_count(dev) > 3 || !libevdev_has_event_code(dev->evdev, EV_KEY, BTN_TOOL_TRIPLETAP)) return; @@ -2029,14 +2026,48 @@ START_TEST(touchpad_3fg_tap_slot_release_btntool) } END_TEST +START_TEST(touchpad_3fg_tap_after_scroll) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + + if (litest_slot_count(dev) <= 3) + return; + + litest_enable_2fg_scroll(dev); + litest_enable_tap(dev->libinput_device); + + litest_touch_down(dev, 0, 40, 20); + litest_touch_down(dev, 1, 50, 20); + litest_drain_events(li); + + /* 2fg scroll */ + litest_touch_move_two_touches(dev, 40, 20, 50, 20, 0, 20, 10); + litest_drain_events(li); + + litest_timeout_tap(); + libinput_dispatch(li); + + /* third finger tap without the other two fingers moving */ + litest_touch_down(dev, 2, 60, 40); + libinput_dispatch(li); + litest_touch_up(dev, 2); + libinput_dispatch(li); + + litest_timeout_tap(); + libinput_dispatch(li); + + litest_assert_empty_queue(li); +} +END_TEST + START_TEST(touchpad_4fg_tap) { struct litest_device *dev = litest_current_device(); struct libinput *li = dev->libinput; int i; - if (libevdev_get_abs_maximum(dev->evdev, - ABS_MT_SLOT) <= 3) + if (litest_slot_count(dev) <= 4) return; litest_enable_tap(dev->libinput_device); @@ -2067,8 +2098,7 @@ 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) + if (litest_slot_count(dev) <= 4) return; litest_enable_tap(dev->libinput_device); @@ -2099,14 +2129,85 @@ START_TEST(touchpad_4fg_tap_quickrelease) } END_TEST +START_TEST(touchpad_move_after_touch) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + int nfingers = _i; /* ranged test */ + + if (nfingers > litest_slot_count(dev)) + return; + + litest_enable_tap(dev->libinput_device); + litest_drain_events(li); + + /* respective number of fingers down */ + switch(nfingers) { + case 5: + litest_touch_down(dev, 4, 70, 30); + /* fallthrough */ + case 4: + litest_touch_down(dev, 3, 70, 30); + /* fallthrough */ + case 3: + litest_touch_down(dev, 2, 60, 30); + /* fallthrough */ + case 2: + litest_touch_down(dev, 1, 50, 30); + /* fallthrough */ + case 1: + litest_touch_down(dev, 0, 40, 30); + /* fallthrough */ + break; + default: + abort(); + } + + /* move finger 1 */ + libinput_dispatch(li); + litest_touch_move_to(dev, 0, 70, 30, 70, 60, 10); + libinput_dispatch(li); + + /* lift finger 1, put it back */ + litest_touch_up(dev, 0); + libinput_dispatch(li); + litest_touch_down(dev, 0, 40, 30); + libinput_dispatch(li); + + /* lift fingers up */ + switch(nfingers) { + case 5: + litest_touch_up(dev, 4); + /* fallthrough */ + case 4: + litest_touch_up(dev, 3); + /* fallthrough */ + case 3: + litest_touch_up(dev, 2); + /* fallthrough */ + case 2: + litest_touch_up(dev, 1); + /* fallthrough */ + case 1: + litest_touch_up(dev, 0); + /* fallthrough */ + break; + } + libinput_dispatch(li); + litest_timeout_tap(); + libinput_dispatch(li); + + litest_assert_no_typed_events(li, LIBINPUT_EVENT_POINTER_BUTTON); +} +END_TEST + START_TEST(touchpad_5fg_tap) { struct litest_device *dev = litest_current_device(); struct libinput *li = dev->libinput; int i; - if (libevdev_get_abs_maximum(dev->evdev, - ABS_MT_SLOT) <= 4) + if (litest_slot_count(dev) < 5) return; litest_enable_tap(dev->libinput_device); @@ -2139,8 +2240,7 @@ 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) + if (litest_slot_count(dev) < 5) return; litest_enable_tap(dev->libinput_device); @@ -3104,7 +3204,7 @@ START_TEST(touchpad_tap_palm_on_touch_3) int which = _i; /* ranged test */ int this = which % 3; - if (libevdev_get_abs_maximum(dev->evdev, ABS_MT_SLOT) <= 3) + if (litest_slot_count(dev) < 3) return; if (!touchpad_has_palm_pressure(dev)) @@ -3150,7 +3250,7 @@ START_TEST(touchpad_tap_palm_on_touch_3_retouch) int which = _i; /* ranged test */ int this = which % 3; - if (libevdev_get_abs_maximum(dev->evdev, ABS_MT_SLOT) <= 3) + if (litest_slot_count(dev) < 3) return; if (!touchpad_has_palm_pressure(dev)) @@ -3201,7 +3301,7 @@ START_TEST(touchpad_tap_palm_on_touch_4) int which = _i; /* ranged test */ int this = which % 4; - if (libevdev_get_abs_maximum(dev->evdev, ABS_MT_SLOT) <= 4) + if (litest_slot_count(dev) < 4) return; if (!touchpad_has_palm_pressure(dev)) @@ -3390,7 +3490,10 @@ START_TEST(touchpad_tap_palm_multitap_down_again) msleep(10); } - for (ntaps = 0; ntaps <= 2 * range; ntaps++) { + litest_timeout_tap(); + libinput_dispatch(li); + + for (ntaps = 0; ntaps <= 2 * range + 1; ntaps++) { litest_assert_button_event(li, BTN_LEFT, LIBINPUT_BUTTON_STATE_PRESSED); @@ -3551,6 +3654,40 @@ START_TEST(touchpad_tap_palm_dwt_tap) } END_TEST +START_TEST(touchpad_tap_palm_3fg_start) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + + if (litest_slot_count(dev) < 3 || + !litest_has_palm_detect_size(dev)) + return; + + litest_enable_tap(dev->libinput_device); + litest_drain_events(li); + + litest_push_event_frame(dev); + litest_touch_down(dev, 0, 50, 50); + litest_touch_down(dev, 1, 55, 55); + litest_touch_down(dev, 2, 99, 55); /* edge palm */ + litest_pop_event_frame(dev); + libinput_dispatch(li); + + litest_touch_up(dev, 2); /* release the palm */ + litest_assert_empty_queue(li); + + litest_touch_up(dev, 0); + litest_touch_up(dev, 1); + + libinput_dispatch(li); + litest_assert_button_event(li, BTN_RIGHT, + LIBINPUT_BUTTON_STATE_PRESSED); + litest_assert_button_event(li, BTN_RIGHT, + LIBINPUT_BUTTON_STATE_RELEASED); + litest_assert_empty_queue(li); +} +END_TEST + TEST_COLLECTION(touchpad_tap) { struct range any_tap_range = {1, 4}; @@ -3560,6 +3697,7 @@ TEST_COLLECTION(touchpad_tap) struct range range_2fg = {0, 2}; struct range range_3fg = {0, 3}; struct range range_4fg = {0, 4}; + struct range range_multifinger = {2, 5}; litest_add("tap:1fg", touchpad_1fg_tap, LITEST_TOUCHPAD, LITEST_ANY); litest_add("tap:1fg", touchpad_1fg_doubletap, LITEST_TOUCHPAD, LITEST_ANY); @@ -3600,12 +3738,15 @@ TEST_COLLECTION(touchpad_tap) litest_add("tap:3fg", touchpad_3fg_tap_pressure_btntool, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH); litest_add_for_device("tap:3fg", touchpad_3fg_tap_btntool_pointerjump, LITEST_SYNAPTICS_TOPBUTTONPAD); litest_add_for_device("tap:3fg", touchpad_3fg_tap_slot_release_btntool, LITEST_SYNAPTICS_TOPBUTTONPAD); + litest_add("tap:3fg", touchpad_3fg_tap_after_scroll, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH); litest_add("tap:4fg", touchpad_4fg_tap, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH|LITEST_SEMI_MT); litest_add("tap:4fg", touchpad_4fg_tap_quickrelease, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH|LITEST_SEMI_MT); litest_add("tap:5fg", touchpad_5fg_tap, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH|LITEST_SEMI_MT); litest_add("tap:5fg", touchpad_5fg_tap_quickrelease, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH|LITEST_SEMI_MT); + litest_add_ranged("tap:multifinger", touchpad_move_after_touch, LITEST_TOUCHPAD, LITEST_ANY, &range_multifinger); + /* Real buttons don't interfere with tapping, so don't run those for pads with buttons */ litest_add("tap:1fg", touchpad_1fg_double_tap_click, LITEST_CLICKPAD, LITEST_ANY); @@ -3662,4 +3803,5 @@ TEST_COLLECTION(touchpad_tap) litest_add_ranged("tap:palm", touchpad_tap_palm_multitap_click, LITEST_TOUCHPAD, LITEST_ANY, &multitap_range); litest_add("tap:palm", touchpad_tap_palm_click_then_tap, LITEST_TOUCHPAD, LITEST_ANY); litest_add("tap:palm", touchpad_tap_palm_dwt_tap, LITEST_TOUCHPAD, LITEST_ANY); + litest_add("tap:palm", touchpad_tap_palm_3fg_start, LITEST_TOUCHPAD, LITEST_ANY); } diff --git a/test/test-touchpad.c b/test/test-touchpad.c index 73dd9f0..96cb8db 100644 --- a/test/test-touchpad.c +++ b/test/test-touchpad.c @@ -1157,34 +1157,12 @@ START_TEST(touchpad_edge_scroll_into_area) END_TEST static bool -touchpad_has_palm_detect_size(struct litest_device *dev) -{ - double width, height; - unsigned int vendor; - unsigned int bustype; - int rc; - - vendor = libinput_device_get_id_vendor(dev->libinput_device); - bustype = libevdev_get_id_bustype(dev->evdev); - if (vendor == VENDOR_ID_WACOM) - return 0; - if (bustype == BUS_BLUETOOTH) - return 0; - if (vendor == VENDOR_ID_APPLE) - return 1; - - rc = libinput_device_get_size(dev->libinput_device, &width, &height); - - return rc == 0 && width >= 70; -} - -static bool touchpad_has_top_palm_detect_size(struct litest_device *dev) { double width, height; int rc; - if (!touchpad_has_palm_detect_size(dev)) + if (!litest_has_palm_detect_size(dev)) return false; rc = libinput_device_get_size(dev->libinput_device, &width, &height); @@ -1197,7 +1175,7 @@ START_TEST(touchpad_palm_detect_at_edge) struct litest_device *dev = litest_current_device(); struct libinput *li = dev->libinput; - if (!touchpad_has_palm_detect_size(dev) || + if (!litest_has_palm_detect_size(dev) || !litest_has_2fg_scroll(dev)) return; @@ -1246,7 +1224,7 @@ START_TEST(touchpad_no_palm_detect_at_edge_for_edge_scrolling) struct litest_device *dev = litest_current_device(); struct libinput *li = dev->libinput; - if (!touchpad_has_palm_detect_size(dev)) + if (!litest_has_palm_detect_size(dev)) return; litest_enable_edge_scroll(dev); @@ -1266,7 +1244,7 @@ START_TEST(touchpad_palm_detect_at_bottom_corners) struct litest_device *dev = litest_current_device(); struct libinput *li = dev->libinput; - if (!touchpad_has_palm_detect_size(dev) || + if (!litest_has_palm_detect_size(dev) || !litest_has_2fg_scroll(dev)) return; @@ -1295,7 +1273,7 @@ START_TEST(touchpad_palm_detect_at_top_corners) struct litest_device *dev = litest_current_device(); struct libinput *li = dev->libinput; - if (!touchpad_has_palm_detect_size(dev) || + if (!litest_has_palm_detect_size(dev) || !litest_has_2fg_scroll(dev)) return; @@ -1326,7 +1304,7 @@ START_TEST(touchpad_palm_detect_palm_stays_palm) struct litest_device *dev = litest_current_device(); struct libinput *li = dev->libinput; - if (!touchpad_has_palm_detect_size(dev) || + if (!litest_has_palm_detect_size(dev) || !litest_has_2fg_scroll(dev)) return; @@ -1368,7 +1346,7 @@ START_TEST(touchpad_palm_detect_palm_becomes_pointer) struct litest_device *dev = litest_current_device(); struct libinput *li = dev->libinput; - if (!touchpad_has_palm_detect_size(dev) || + if (!litest_has_palm_detect_size(dev) || !litest_has_2fg_scroll(dev)) return; @@ -1419,7 +1397,7 @@ START_TEST(touchpad_palm_detect_no_palm_moving_into_edges) struct litest_device *dev = litest_current_device(); struct libinput *li = dev->libinput; - if (!touchpad_has_palm_detect_size(dev)) + if (!litest_has_palm_detect_size(dev)) return; litest_disable_tap(dev->libinput_device); @@ -1498,7 +1476,7 @@ START_TEST(touchpad_palm_detect_tap_hardbuttons) struct litest_device *dev = litest_current_device(); struct libinput *li = dev->libinput; - if (!touchpad_has_palm_detect_size(dev)) + if (!litest_has_palm_detect_size(dev)) return; litest_enable_tap(dev->libinput_device); @@ -1540,7 +1518,7 @@ START_TEST(touchpad_palm_detect_tap_softbuttons) struct litest_device *dev = litest_current_device(); struct libinput *li = dev->libinput; - if (!touchpad_has_palm_detect_size(dev)) + if (!litest_has_palm_detect_size(dev)) return; litest_enable_tap(dev->libinput_device); @@ -1595,7 +1573,7 @@ START_TEST(touchpad_palm_detect_tap_clickfinger) struct litest_device *dev = litest_current_device(); struct libinput *li = dev->libinput; - if (!touchpad_has_palm_detect_size(dev)) + if (!litest_has_palm_detect_size(dev)) return; litest_enable_tap(dev->libinput_device); @@ -1638,7 +1616,7 @@ START_TEST(touchpad_no_palm_detect_2fg_scroll) struct litest_device *dev = litest_current_device(); struct libinput *li = dev->libinput; - if (!touchpad_has_palm_detect_size(dev) || + if (!litest_has_palm_detect_size(dev) || !litest_has_2fg_scroll(dev)) return; @@ -1665,7 +1643,7 @@ START_TEST(touchpad_palm_detect_both_edges) struct litest_device *dev = litest_current_device(); struct libinput *li = dev->libinput; - if (!touchpad_has_palm_detect_size(dev) || + if (!litest_has_palm_detect_size(dev) || !litest_has_2fg_scroll(dev)) return; @@ -2060,7 +2038,7 @@ START_TEST(touchpad_palm_detect_pressure_after_edge) }; if (!touchpad_has_palm_pressure(dev) || - !touchpad_has_palm_detect_size(dev) || + !litest_has_palm_detect_size(dev) || !litest_has_2fg_scroll(dev)) return; @@ -2608,6 +2586,7 @@ touchpad_has_rotation(struct libevdev *evdev) START_TEST(touchpad_left_handed_rotation) { +#if HAVE_LIBWACOM struct litest_device *dev = litest_current_device(); struct libinput_device *d = dev->libinput_device; struct libinput *li = dev->libinput; @@ -2655,6 +2634,7 @@ START_TEST(touchpad_left_handed_rotation) libinput_event_destroy(event); } while ((event = libinput_get_event(li))); +#endif } END_TEST @@ -3566,7 +3546,123 @@ START_TEST(touchpad_initial_state) libinput_event_destroy(ev2); } - libinput_unref(libinput2); + litest_destroy_context(libinput2); +} +END_TEST + +START_TEST(touchpad_fingers_down_before_init) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li; + + int finger_count = _i; /* looped test */ + unsigned int map[] = {0, BTN_TOOL_PEN, BTN_TOOL_DOUBLETAP, + BTN_TOOL_TRIPLETAP, BTN_TOOL_QUADTAP, + BTN_TOOL_QUINTTAP}; + + if (!libevdev_has_event_code(dev->evdev, EV_KEY, map[finger_count])) + return; + + /* Fingers down but before we have the real context */ + for (int i = 0; i < finger_count; i++) { + if (litest_slot_count(dev) >= finger_count) { + litest_touch_down(dev, i, 20 + 10 * i, 30); + } else { + litest_event(dev, EV_KEY, map[finger_count], 1); + } + } + + litest_drain_events(dev->libinput); + + /* create anew context that already has the fingers down */ + li = litest_create_context(); + libinput_path_add_device(li, + libevdev_uinput_get_devnode(dev->uinput)); + litest_drain_events(li); + + for (int x = 0; x < 10; x++) { + for (int i = 0; i < finger_count; i++) { + if (litest_slot_count(dev) < finger_count) + break; + litest_touch_move(dev, i, 20 + 10 * i + x, 30); + } + } + libinput_dispatch(li); + litest_assert_empty_queue(li); + + for (int i = 0; i < finger_count; i++) { + if (litest_slot_count(dev) >= finger_count) { + litest_touch_up(dev, i); + } else { + litest_event(dev, EV_KEY, map[finger_count], 0); + } + } + + litest_assert_empty_queue(li); + + litest_destroy_context(li); +} +END_TEST + + +/* This just tests that we don't completely screw up in one specific case. + * The test likely needs to be removed if it starts failing in the future. + * + * Where we get touch releases during SYN_DROPPED, libevdev < 1.9.0 gives us + * wrong event sequence during sync, see + * https://gitlab.freedesktop.org/libevdev/libevdev/merge_requests/19 + * + * libinput 1.15.1 ended up dropping our slot count to 0, making the + * touchpad unusable, see #422. This test just checks that we can still move + * the pointer and scroll where we trigger such a sequence. This tests for + * the worst-case scenario - where we previously reset to a slot count of 0. + * + * However, the exact behavior depends on how many slots were + * stopped/restarted during SYN_DROPPED, a single test is barely useful. + * libinput will still do the wrong thing if you start with e.g. 3fg on the + * touchpad and release one or two of them. But since this needs to be fixed + * in libevdev, here is the most important test. + */ +START_TEST(touchpad_state_after_syn_dropped_2fg_change) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + + litest_drain_events(li); + litest_disable_tap(dev->libinput_device); + + litest_touch_down(dev, 0, 10, 10); + libinput_dispatch(li); + + /* Force a SYN_DROPPED */ + for (int i = 0; i < 500; i++) + litest_touch_move(dev, 0, 10 + 0.1 * i, 10 + 0.1 * i); + + /* still within SYN_DROPPED */ + litest_touch_up(dev, 0); + litest_touch_down(dev, 0, 50, 50); + litest_touch_down(dev, 1, 70, 50); + + libinput_dispatch(li); + litest_drain_events(li); + + litest_touch_up(dev, 0); + litest_touch_up(dev, 1); + + /* 2fg scrolling still works? */ + litest_touch_down(dev, 0, 50, 50); + litest_touch_down(dev, 1, 70, 50); + litest_touch_move_two_touches(dev, 50, 50, 70, 50, 0, -20, 10); + litest_touch_up(dev, 0); + litest_touch_up(dev, 1); + litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_AXIS); + + /* pointer motion still works? */ + litest_touch_down(dev, 0, 50, 50); + for (int i = 0; i < 10; i++) + litest_touch_move(dev, 0, 10 + 0.1 * i, 10 + 0.1 * i); + litest_touch_up(dev, 0); + litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION); } END_TEST @@ -5517,7 +5613,7 @@ START_TEST(touchpad_finger_always_down) litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION); - libinput_unref(li); + litest_destroy_context(li); } END_TEST @@ -6057,7 +6153,7 @@ START_TEST(touchpad_pressure_btntool) libinput_dispatch(li); /* make one finger real */ - litest_touch_move_to(dev, 0, 40, 50, 41, 52, 10); + litest_touch_move(dev, 0, 40, 50); litest_drain_events(li); /* tripletap should now be 3 fingers tap */ @@ -6318,7 +6414,7 @@ START_TEST(touchpad_palm_detect_touch_size_after_edge) if (!touchpad_has_touch_size(dev) || litest_touchpad_is_external(dev) || - !touchpad_has_palm_detect_size(dev) || + !litest_has_palm_detect_size(dev) || !litest_has_2fg_scroll(dev)) return; @@ -6554,6 +6650,9 @@ START_TEST(touchpad_suspend_abba) tabletmode = litest_add_device(li, LITEST_THINKPAD_EXTRABUTTONS); extmouse = litest_add_device(li, LITEST_MOUSE); + litest_grab_device(lid); + litest_grab_device(tabletmode); + litest_disable_tap(tp->libinput_device); /* ABBA test for touchpad internal suspend: @@ -6667,6 +6766,8 @@ START_TEST(touchpad_suspend_abba) } out: + litest_ungrab_device(lid); + litest_ungrab_device(tabletmode); litest_delete_device(lid); litest_delete_device(tabletmode); litest_delete_device(extmouse); @@ -6687,6 +6788,8 @@ START_TEST(touchpad_suspend_abab) lid = litest_add_device(li, LITEST_LID_SWITCH); tabletmode = litest_add_device(li, LITEST_THINKPAD_EXTRABUTTONS); extmouse = litest_add_device(li, LITEST_MOUSE); + litest_grab_device(lid); + litest_grab_device(tabletmode); litest_disable_tap(tp->libinput_device); @@ -6818,6 +6921,8 @@ START_TEST(touchpad_suspend_abab) } out: + litest_ungrab_device(lid); + litest_ungrab_device(tabletmode); litest_delete_device(lid); litest_delete_device(tabletmode); litest_delete_device(extmouse); @@ -6879,6 +6984,7 @@ TEST_COLLECTION(touchpad) struct range suspends = { SUSPEND_EXT_MOUSE, SUSPEND_COUNT }; struct range axis_range = {ABS_X, ABS_Y + 1}; struct range twice = {0, 2 }; + struct range five_fingers = {1, 6}; litest_add("touchpad:motion", touchpad_1fg_motion, LITEST_TOUCHPAD, LITEST_ANY); litest_add("touchpad:motion", touchpad_2fg_no_motion, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH); @@ -6991,6 +7097,8 @@ TEST_COLLECTION(touchpad) litest_add_for_device("touchpad:trackpoint", touchpad_trackpoint_no_trackpoint, LITEST_SYNAPTICS_TRACKPOINT_BUTTONS); litest_add_ranged("touchpad:state", touchpad_initial_state, LITEST_TOUCHPAD, LITEST_ANY, &axis_range); + litest_add_ranged("touchpad:state", touchpad_fingers_down_before_init, LITEST_TOUCHPAD, LITEST_ANY, &five_fingers); + litest_add("touchpad:state", touchpad_state_after_syn_dropped_2fg_change, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH); litest_add("touchpad:dwt", touchpad_dwt, LITEST_TOUCHPAD, LITEST_ANY); litest_add_for_device("touchpad:dwt", touchpad_dwt_ext_and_int_keyboard, LITEST_SYNAPTICS_I2C); diff --git a/test/test-util-includes.c b/test/test-util-includes.c new file mode 100644 index 0000000..6cb3419 --- /dev/null +++ b/test/test-util-includes.c @@ -0,0 +1,6 @@ +/* compile test for the util files */ +#include @FILE@ + +int main(void) { + return 0; +} diff --git a/test/test-utils.c b/test/test-utils.c index 0cde5aa..5faec0e 100644 --- a/test/test-utils.c +++ b/test/test-utils.c @@ -24,11 +24,18 @@ #include #include -#include #include -#include "libinput-util.h" +#include "util-list.h" +#include "util-strings.h" +#include "util-time.h" +#include "util-prop-parsers.h" +#include "util-macros.h" +#include "util-bits.h" +#include "util-ratelimit.h" +#include "util-matrix.h" + #define TEST_VERSIONSORT #include "libinput-versionsort.h" @@ -539,6 +546,83 @@ START_TEST(evcode_prop_parser) } END_TEST +START_TEST(evdev_abs_parser) +{ + struct test { + uint32_t which; + const char *prop; + int min, max, res, fuzz, flat; + + } tests[] = { + { .which = (ABS_MASK_MIN|ABS_MASK_MAX), + .prop = "1:2", + .min = 1, .max = 2 }, + { .which = (ABS_MASK_MIN|ABS_MASK_MAX), + .prop = "1:2:", + .min = 1, .max = 2 }, + { .which = (ABS_MASK_MIN|ABS_MASK_MAX|ABS_MASK_RES), + .prop = "10:20:30", + .min = 10, .max = 20, .res = 30 }, + { .which = (ABS_MASK_RES), + .prop = "::100", + .res = 100 }, + { .which = (ABS_MASK_MIN), + .prop = "10:", + .min = 10 }, + { .which = (ABS_MASK_MAX|ABS_MASK_RES), + .prop = ":10:1001", + .max = 10, .res = 1001 }, + { .which = (ABS_MASK_MIN|ABS_MASK_MAX|ABS_MASK_RES|ABS_MASK_FUZZ), + .prop = "1:2:3:4", + .min = 1, .max = 2, .res = 3, .fuzz = 4}, + { .which = (ABS_MASK_MIN|ABS_MASK_MAX|ABS_MASK_RES|ABS_MASK_FUZZ|ABS_MASK_FLAT), + .prop = "1:2:3:4:5", + .min = 1, .max = 2, .res = 3, .fuzz = 4, .flat = 5}, + { .which = (ABS_MASK_MIN|ABS_MASK_RES|ABS_MASK_FUZZ|ABS_MASK_FLAT), + .prop = "1::3:4:50", + .min = 1, .res = 3, .fuzz = 4, .flat = 50}, + { .which = ABS_MASK_FUZZ|ABS_MASK_FLAT, + .prop = ":::5:60", + .fuzz = 5, .flat = 60}, + { .which = ABS_MASK_FUZZ, + .prop = ":::5:", + .fuzz = 5 }, + { .which = ABS_MASK_RES, .prop = "::12::", + .res = 12 }, + /* Malformed property but parsing this one makes us more + * future proof */ + { .which = (ABS_MASK_RES|ABS_MASK_FUZZ|ABS_MASK_FLAT), + .prop = "::12:1:2:3:4:5:6", + .res = 12, .fuzz = 1, .flat = 2 }, + { .which = 0, .prop = ":::::" }, + { .which = 0, .prop = ":" }, + { .which = 0, .prop = "" }, + { .which = 0, .prop = ":asb::::" }, + { .which = 0, .prop = "foo" }, + }; + struct test *t; + + ARRAY_FOR_EACH(tests, t) { + struct input_absinfo abs; + uint32_t mask; + + mask = parse_evdev_abs_prop(t->prop, &abs); + ck_assert_int_eq(mask, t->which); + + if (t->which & ABS_MASK_MIN) + ck_assert_int_eq(abs.minimum, t->min); + if (t->which & ABS_MASK_MAX) + ck_assert_int_eq(abs.maximum, t->max); + if (t->which & ABS_MASK_RES) + ck_assert_int_eq(abs.resolution, t->res); + if (t->which & ABS_MASK_FUZZ) + ck_assert_int_eq(abs.fuzz, t->fuzz); + if (t->which & ABS_MASK_FLAT) + ck_assert_int_eq(abs.flat, t->flat); + } +} +END_TEST + START_TEST(time_conversion) { ck_assert_int_eq(us(10), 10); @@ -549,6 +633,37 @@ START_TEST(time_conversion) } END_TEST +START_TEST(human_time) +{ + struct ht_tests { + uint64_t interval; + unsigned int value; + const char *unit; + } tests[] = { + { 0, 0, "us" }, + { 123, 123, "us" }, + { ms2us(5), 5, "ms" }, + { ms2us(100), 100, "ms" }, + { s2us(5), 5, "s" }, + { s2us(100), 100, "s" }, + { s2us(120), 2, "min" }, + { 5 * s2us(60), 5, "min" }, + { 120 * s2us(60), 2, "h" }, + { 5 * 60 * s2us(60), 5, "h" }, + { 48 * 60 * s2us(60), 2, "d" }, + { 1000 * 24 * 60 * s2us(60), 1000, "d" }, + { 0, 0, NULL }, + }; + for (int i = 0; tests[i].unit != NULL; i++) { + struct human_time ht; + + ht = to_human_time(tests[i].interval); + ck_assert_int_eq(ht.value, tests[i].value); + ck_assert_str_eq(ht.unit, tests[i].unit); + } +} +END_TEST + struct atoi_test { char *str; bool success; @@ -949,6 +1064,92 @@ START_TEST(strjoin_test) } END_TEST +START_TEST(strstrip_test) +{ + struct strstrip_test { + const char *string; + const char *expected; + const char *what; + } tests[] = { + { "foo", "foo", "1234" }, + { "\"bar\"", "bar", "\"" }, + { "'bar'", "bar", "'" }, + { "\"bar\"", "\"bar\"", "'" }, + { "'bar'", "'bar'", "\"" }, + { "\"bar\"", "bar", "\"" }, + { "\"\"", "", "\"" }, + { "\"foo\"bar\"", "foo\"bar", "\"" }, + { "\"'foo\"bar\"", "foo\"bar", "\"'" }, + { "abcfooabcbarbca", "fooabcbar", "abc" }, + { "xxxxfoo", "foo", "x" }, + { "fooyyyy", "foo", "y" }, + { "xxxxfooyyyy", "foo", "xy" }, + { "x xfooy y", " xfooy ", "xy" }, + { " foo\n", "foo", " \n" }, + { "", "", "abc" }, + { "", "", "" }, + { NULL , NULL, NULL } + }; + struct strstrip_test *t = tests; + + while (t->string) { + char *str; + str = strstrip(t->string, t->what); + ck_assert_str_eq(str, t->expected); + free(str); + t++; + } +} +END_TEST + +START_TEST(strendswith_test) +{ + struct strendswith_test { + const char *string; + const char *suffix; + bool expected; + } tests[] = { + { "foobar", "bar", true }, + { "foobar", "foo", false }, + { "foobar", "foobar", true }, + { "foo", "foobar", false }, + { "foobar", "", false }, + { "", "", false }, + { "", "foo", false }, + { NULL, NULL, false }, + }; + + for (struct strendswith_test *t = tests; t->string; t++) { + ck_assert_int_eq(strendswith(t->string, t->suffix), + t->expected); + } +} +END_TEST + +START_TEST(strstartswith_test) +{ + struct strstartswith_test { + const char *string; + const char *suffix; + bool expected; + } tests[] = { + { "foobar", "foo", true }, + { "foobar", "bar", false }, + { "foobar", "foobar", true }, + { "foo", "foobar", false }, + { "foo", "", false }, + { "", "", false }, + { "foo", "", false }, + { NULL, NULL, false }, + }; + + for (struct strstartswith_test *t = tests; t->string; t++) { + ck_assert_int_eq(strstartswith(t->string, t->suffix), + t->expected); + } +} +END_TEST + START_TEST(list_test_insert) { struct list_test { @@ -1043,6 +1244,7 @@ litest_utils_suite(void) tcase_add_test(tc, calibration_prop_parser); tcase_add_test(tc, range_prop_parser); tcase_add_test(tc, evcode_prop_parser); + tcase_add_test(tc, evdev_abs_parser); tcase_add_test(tc, safe_atoi_test); tcase_add_test(tc, safe_atoi_base_16_test); tcase_add_test(tc, safe_atoi_base_8_test); @@ -1053,12 +1255,18 @@ litest_utils_suite(void) tcase_add_test(tc, strsplit_test); tcase_add_test(tc, kvsplit_double_test); tcase_add_test(tc, strjoin_test); + tcase_add_test(tc, strstrip_test); + tcase_add_test(tc, strendswith_test); + tcase_add_test(tc, strstartswith_test); tcase_add_test(tc, time_conversion); + tcase_add_test(tc, human_time); tcase_add_test(tc, list_test_insert); tcase_add_test(tc, list_test_append); tcase_add_test(tc, strverscmp_test); + suite_add_tcase(s, tc); + return s; } @@ -1068,14 +1276,8 @@ int main(int argc, char **argv) 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 (RUNNING_ON_VALGRIND) - return 77; - s = litest_utils_suite(); - sr = srunner_create(s); + sr = srunner_create(s); srunner_run_all(sr, CK_ENV); nfailed = srunner_ntests_failed(sr); diff --git a/test/valgrind.suppressions b/test/valgrind.suppressions index 7ca3b5b..e754145 100644 --- a/test/valgrind.suppressions +++ b/test/valgrind.suppressions @@ -60,6 +60,13 @@ fun:libevdev_grab } { + ioctl:grab + Memcheck:Param + ioctl(generic) + fun:ioctl + fun:grab_device +} +{ bash:execute_command Memcheck:Cond ... diff --git a/tools/libinput-analyze-per-slot-delta.man b/tools/libinput-analyze-per-slot-delta.man new file mode 100644 index 0000000..b4c3e37 --- /dev/null +++ b/tools/libinput-analyze-per-slot-delta.man @@ -0,0 +1,87 @@ +.TH libinput-analyze-per-slot-delta "1" +.SH NAME +libinput\-analyze\-per\-slot\-delta \- analyze the per-event delta movement for touch slots +.SH SYNOPSIS +.B libinput analyze per-slot-delta [\-\-help] [options] \fIrecording.yml\fI +.SH DESCRIPTION +.PP +The +.B "libinput analyze per\-slot\-delta" +tool analyzes a recording made with +.B "libinput record" +and prints the delta movement per touch slot. +.PP +This is a debugging tool only, its output may change at any time. Do not +rely on the output. +.SH OPTIONS +.TP 8 +.B \-\-help +Print help +.TP 8 +.B \-\-ignore-below= +Ignore any movement below the given threshold. The threshold is in +mm if \fB\-\-use-mm\fR is selected or in device units otherwise. +.TP 8 +.B \-\-threshold= +Color any movement above this threshold in red. The threshold is in +mm if \fB\-\-use-mm\fR is selected or in device units otherwise. +.TP 8 +.B \-\-use-mm +Print data in mm instead of device units +.TP 8 +.B \-\-use-st +Use the single-touch ABS_X/ABS_Y instead of the multitouch axes +.TP 8 +.B \-\-use-absolute +Print absolute coordinates, not deltas +.SH OUTPUT +An example output for a single finger touch on a touchpad supporting two +slots is below. This output is with the use of the +.B --use-mm +flag. +.PP +.nf +.sf + 0.000000 +0ms TOU: ++++++ | ************* | + 0.021900 +21ms TOU: →↘ +1.10/+0.14 | ************* | + 0.033468 +11ms TOU: →↘ +1.15/+0.19 | ************* | + 0.043856 +10ms TOU: →↘ +1.76/+0.22 | ************* | + 0.053237 +9ms TOU: →↘ +2.20/+0.19 | ************* | +.fi +.in +.PP +The entry +.B ++++++ +indicates a finger has been put down, +.B ------ +indicates the finger has lifted. +The left-most column is the absolute timestamp in seconds.microseconds +followed by the relative time of the event to the previous event. +.PP +The word +.B TOU +in this example represents +BTN_TOUCH, similar abbreviations exist for +BTN_TOOL_DOUBLETAP, BTN_TOOL_TRIPLETAP, BTN_TOOL_QUADTAP, and +BTN_TOOL_QUINTTAP. +.PP +The arrows +indicate the approximate direction on a 16-point compass, in this example +EastSouthEast. +.PP +Each multitouch slot supported by the hardware has one column, where the +column shows asterisk +.B ******** +no finger is down for that slot. Where the column shows spaces only, a +finger is down but no data is currently available. +.PP +In the above example, the third events occurs ~33ms into the recording, is +11ms after the previous event and has an east south-east direction. The +movement for this event was x: 1.15 and y: 0.19 mm. A second finger is not +currently down. +.SH LIBINPUT +Part of the +.B libinput(1) +suite + + diff --git a/tools/libinput-analyze-per-slot-delta.py b/tools/libinput-analyze-per-slot-delta.py new file mode 100755 index 0000000..bdafeff --- /dev/null +++ b/tools/libinput-analyze-per-slot-delta.py @@ -0,0 +1,333 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 +# vim: set expandtab shiftwidth=4: +# -*- Mode: python; coding: utf-8; indent-tabs-mode: nil -*- */ +# +# Copyright © 2018 Red Hat, Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the 'Software'), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice (including the next +# paragraph) shall be included in all copies or substantial portions of the +# Software. +# +# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. +# +# +# Measures the relative motion between touch events (based on slots) +# +# Input is a libinput record yaml file + +import argparse +import math +import sys +import yaml +import libevdev + + +COLOR_RESET = '\x1b[0m' +COLOR_RED = '\x1b[6;31m' + + +class SlotFormatter(): + width = 16 + + def __init__(self, is_absolute=False, resolution=None, + threshold=None, ignore_below=None): + self.threshold = threshold + self.ignore_below = ignore_below + self.resolution = resolution + self.is_absolute = is_absolute + self.slots = [] + self.have_data = False + self.filtered = False + + def __str__(self): + return ' | '.join(self.slots) + + def format_slot(self, slot): + if slot.state == SlotState.BEGIN: + self.slots.append('+++++++'.center(self.width)) + self.have_data = True + elif slot.state == SlotState.END: + self.slots.append('-------'.center(self.width)) + self.have_data = True + elif slot.state == SlotState.NONE: + self.slots.append(('*' * (self.width - 2)).center(self.width)) + elif not slot.dirty: + self.slots.append(' '.center(self.width)) + else: + if self.resolution is not None: + dx, dy = slot.dx / self.resolution[0], slot.dy / self.resolution[1] + else: + dx, dy = slot.dx, slot.dy + if dx != 0 and dy != 0: + t = math.atan2(dx, dy) + t += math.pi # in [0, 2pi] range now + + if t == 0: + t = 0.01 + else: + t = t * 180.0 / math.pi + + directions = ['↖↑', '↖←', '↙←', '↙↓', '↓↘', '→↘', '→↗', '↑↗'] + direction = directions[int(t / 45)] + elif dy == 0: + if dx < 0: + direction = '←←' + else: + direction = '→→' + else: + if dy < 0: + direction = '↑↑' + else: + direction = '↓↓' + + color = '' + reset = '' + if not self.is_absolute: + if self.ignore_below is not None or self.threshold is not None: + dist = math.hypot(dx, dy) + if self.ignore_below is not None and dist < self.ignore_below: + self.slots.append(' '.center(self.width)) + self.filtered = True + return + if self.threshold is not None and dist >= self.threshold: + color = COLOR_RED + reset = COLOR_RESET + if isinstance(dx, int) and isinstance(dy, int): + string = "{} {}{:+4d}/{:+4d}{}".format(direction, color, dx, dy, reset) + else: + string = "{} {}{:+3.2f}/{:+03.2f}{}".format(direction, color, dx, dy, reset) + else: + x, y = slot.x, slot.y + string = "{} {}{:4d}/{:4d}{}".format(direction, color, x, y, reset) + self.have_data = True + self.slots.append(string.ljust(self.width + len(color) + len(reset))) + + +class SlotState: + NONE = 0 + BEGIN = 1 + UPDATE = 2 + END = 3 + + +class Slot: + state = SlotState.NONE + x = 0 + y = 0 + dx = 0 + dy = 0 + used = False + dirty = False + + def __init__(self, index): + self.index = index + + +def main(argv): + global COLOR_RESET + global COLOR_RED + + slots = [] + xres, yres = 1, 1 + + parser = argparse.ArgumentParser(description="Measure delta between event frames for each slot") + parser.add_argument("--use-mm", action='store_true', help="Use mm instead of device deltas") + parser.add_argument("--use-st", action='store_true', help="Use ABS_X/ABS_Y instead of ABS_MT_POSITION_X/Y") + parser.add_argument("--use-absolute", action='store_true', help="Use absolute coordinates, not deltas") + parser.add_argument("path", metavar="recording", + nargs=1, help="Path to libinput-record YAML file") + parser.add_argument("--threshold", type=float, default=None, help="Mark any delta above this treshold") + parser.add_argument("--ignore-below", type=float, default=None, help="Ignore any delta below this theshold") + args = parser.parse_args() + + if not sys.stdout.isatty(): + COLOR_RESET = '' + COLOR_RED = '' + + yml = yaml.safe_load(open(args.path[0])) + device = yml['devices'][0] + absinfo = device['evdev']['absinfo'] + try: + nslots = absinfo[libevdev.EV_ABS.ABS_MT_SLOT.value][1] + 1 + except KeyError: + args.use_st = True + + if args.use_st: + nslots = 1 + + slots = [Slot(i) for i in range(0, nslots)] + slots[0].used = True + + if args.use_mm: + xres = 1.0 * absinfo[libevdev.EV_ABS.ABS_X.value][4] + yres = 1.0 * absinfo[libevdev.EV_ABS.ABS_Y.value][4] + if not xres or not yres: + print("Error: device doesn't have a resolution, cannot use mm") + sys.exit(1) + + if args.use_st: + print("Warning: slot coordinates on FINGER/DOUBLETAP change may be incorrect") + slots[0].used = True + + slot = 0 + last_time = None + tool_bits = { + libevdev.EV_KEY.BTN_TOUCH: 0, + libevdev.EV_KEY.BTN_TOOL_DOUBLETAP: 0, + libevdev.EV_KEY.BTN_TOOL_TRIPLETAP: 0, + libevdev.EV_KEY.BTN_TOOL_QUADTAP: 0, + libevdev.EV_KEY.BTN_TOOL_QUINTTAP: 0, + } + + nskipped_lines = 0 + + for event in device['events']: + for evdev in event['evdev']: + s = slots[slot] + e = libevdev.InputEvent(code=libevdev.evbit(evdev[2], evdev[3]), + value=evdev[4], sec=evdev[0], usec=evdev[1]) + + if e.code in tool_bits: + tool_bits[e.code] = e.value + + if args.use_st: + # Note: this relies on the EV_KEY events to come in before the + # x/y events, otherwise the last/first event in each slot will + # be wrong. + if (e.code == libevdev.EV_KEY.BTN_TOOL_FINGER or + e.code == libevdev.EV_KEY.BTN_TOOL_PEN): + slot = 0 + s = slots[slot] + s.dirty = True + if e.value: + s.state = SlotState.BEGIN + else: + s.state = SlotState.END + elif e.code == libevdev.EV_KEY.BTN_TOOL_DOUBLETAP: + if len(slots) > 1: + slot = 1 + s = slots[slot] + s.dirty = True + if e.value: + s.state = SlotState.BEGIN + else: + s.state = SlotState.END + elif e.code == libevdev.EV_ABS.ABS_X: + # If recording started after touch down + if s.state == SlotState.NONE: + s.state = SlotState.BEGIN + s.dx, s.dy = 0, 0 + elif s.state == SlotState.UPDATE: + s.dx = e.value - s.x + s.x = e.value + s.dirty = True + elif e.code == libevdev.EV_ABS.ABS_Y: + # If recording started after touch down + if s.state == SlotState.NONE: + s.state = SlotState.BEGIN + s.dx, s.dy = 0, 0 + elif s.state == SlotState.UPDATE: + s.dy = e.value - s.y + s.y = e.value + s.dirty = True + else: + if e.code == libevdev.EV_ABS.ABS_MT_SLOT: + slot = e.value + s = slots[slot] + s.dirty = True + # bcm5974 cycles through slot numbers, so let's say all below + # our current slot number was used + for sl in slots[:slot + 1]: + sl.used = True + elif e.code == libevdev.EV_ABS.ABS_MT_TRACKING_ID: + if e.value == -1: + s.state = SlotState.END + else: + s.state = SlotState.BEGIN + s.dx = 0 + s.dy = 0 + s.dirty = True + elif e.code == libevdev.EV_ABS.ABS_MT_POSITION_X: + # If recording started after touch down + if s.state == SlotState.NONE: + s.state = SlotState.BEGIN + s.dx, s.dy = 0, 0 + elif s.state == SlotState.UPDATE: + s.dx = e.value - s.x + s.x = e.value + s.dirty = True + elif e.code == libevdev.EV_ABS.ABS_MT_POSITION_Y: + # If recording started after touch down + if s.state == SlotState.NONE: + s.state = SlotState.BEGIN + s.dx, s.dy = 0, 0 + elif s.state == SlotState.UPDATE: + s.dy = e.value - s.y + s.y = e.value + s.dirty = True + + if e.code == libevdev.EV_SYN.SYN_REPORT: + if last_time is None: + last_time = e.sec * 1000000 + e.usec + tdelta = 0 + else: + t = e.sec * 1000000 + e.usec + tdelta = int((t - last_time) / 1000) # ms + last_time = t + + tools = [ + (libevdev.EV_KEY.BTN_TOOL_QUINTTAP, 'QIN'), + (libevdev.EV_KEY.BTN_TOOL_QUADTAP, 'QAD'), + (libevdev.EV_KEY.BTN_TOOL_TRIPLETAP, 'TRI'), + (libevdev.EV_KEY.BTN_TOOL_DOUBLETAP, 'DBL'), + (libevdev.EV_KEY.BTN_TOUCH, 'TOU'), + ] + + for bit, string in tools: + if tool_bits[bit]: + tool_state = string + break + else: + tool_state = ' ' + + fmt = SlotFormatter(is_absolute=args.use_absolute, + resolution=(xres, yres) if args.use_mm else None, + threshold=args.threshold, + ignore_below=args.ignore_below) + for sl in [s for s in slots if s.used]: + fmt.format_slot(sl) + + sl.dirty = False + sl.dx = 0 + sl.dy = 0 + if sl.state == SlotState.BEGIN: + sl.state = SlotState.UPDATE + elif sl.state == SlotState.END: + sl.state = SlotState.NONE + + if fmt.have_data: + if nskipped_lines > 0: + print("") + nskipped_lines = 0 + print("{:2d}.{:06d} {:+5d}ms {}: {}".format(e.sec, e.usec, tdelta, tool_state, fmt)) + elif fmt.filtered: + nskipped_lines += 1 + print("\r", " " * 21, "... {} below threshold".format(nskipped_lines), flush=True, end='') + + +if __name__ == '__main__': + main(sys.argv) diff --git a/tools/libinput-analyze.c b/tools/libinput-analyze.c new file mode 100644 index 0000000..17e67dd --- /dev/null +++ b/tools/libinput-analyze.c @@ -0,0 +1,72 @@ +/* + * Copyright © 2017 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include +#include + +#include "shared.h" + +static inline void +usage(void) +{ + printf("Usage: libinput analyze [--help] \n"); +} + +int +main(int argc, char **argv) +{ + int option_index = 0; + + while (1) { + int c; + static struct option opts[] = { + { "help", no_argument, 0, 'h' }, + { 0, 0, 0, 0} + }; + + c = getopt_long(argc, argv, "+h", opts, &option_index); + if (c == -1) + break; + + switch(c) { + case 'h': + usage(); + return EXIT_SUCCESS; + default: + usage(); + return EXIT_FAILURE; + } + } + + if (optind >= argc) { + usage(); + return EXIT_FAILURE; + } + + argc--; + argv++; + + return tools_exec_command("libinput-analyze", argc, argv); +} diff --git a/tools/libinput-analyze.man b/tools/libinput-analyze.man new file mode 100644 index 0000000..dd55a50 --- /dev/null +++ b/tools/libinput-analyze.man @@ -0,0 +1,30 @@ +.TH libinput-analyze "1" "" "libinput @LIBINPUT_VERSION@" "libinput Manual" +.SH NAME +libinput\-analyze \- analyze device data +.SH SYNOPSIS +.B libinput analyze [\-\-help] \fI []\fR +.SH DESCRIPTION +.PP +The +.B "libinput analyze" +tool analyzes device data. Depending on what is to +be analyzed, this tool may not create a libinput context. +.PP +This is a debugging tool only, its output may change at any time. Do not +rely on the output. +.PP +This tool may need to be run as root to have access to the +/dev/input/eventX nodes. +.SH OPTIONS +.TP 8 +.B \-\-help +Print help +.SH FEATURES +Features that can be analyzed include +.TP 8 +.B libinput\-analyze\-per-slot-delta(1) +analyze the delta per event per slot +.SH LIBINPUT +Part of the +.B libinput(1) +suite diff --git a/tools/libinput-debug-events.c b/tools/libinput-debug-events.c index 23926af..efdc93f 100644 --- a/tools/libinput-debug-events.c +++ b/tools/libinput-debug-events.c @@ -25,22 +25,22 @@ #include #include -#include #include #include #include -#include +#include #include #include #include #include -#include #include "linux/input.h" -#include #include #include +#include "libinput-version.h" +#include "util-strings.h" +#include "util-macros.h" #include "shared.h" static uint32_t start_time; @@ -140,6 +140,9 @@ print_event_header(struct libinput_event *ev) case LIBINPUT_EVENT_TABLET_PAD_STRIP: type = "TABLET_PAD_STRIP"; break; + case LIBINPUT_EVENT_TABLET_PAD_KEY: + type = "TABLET_PAD_KEY"; + break; case LIBINPUT_EVENT_SWITCH_TOGGLE: type = "SWITCH_TOGGLE"; break; @@ -158,7 +161,7 @@ print_event_header(struct libinput_event *ev) static void print_event_time(uint32_t time) { - printq("%+6.2fs ", (time - start_time) / 1000.0); + printq("%+6.3fs ", start_time ? (time - start_time) / 1000.0 : 0); } static inline void @@ -578,7 +581,7 @@ print_proximity_event(struct libinput_event *ev) abort(); } - printq("\t%s (%#" PRIx64 ", id %#" PRIx64 ") %s ", + printq("\t%-8s (%#" PRIx64 ", id %#" PRIx64 ") %s ", tool_str, libinput_tablet_tool_get_serial(tool), libinput_tablet_tool_get_tool_id(tool), @@ -765,6 +768,32 @@ print_tablet_pad_strip_event(struct libinput_event *ev) } static void +print_tablet_pad_key_event(struct libinput_event *ev) +{ + struct libinput_event_tablet_pad *p = libinput_event_get_tablet_pad_event(ev); + enum libinput_key_state state; + uint32_t key; + const char *keyname; + + print_event_time(libinput_event_tablet_pad_get_time(p)); + + key = libinput_event_tablet_pad_get_key(p); + if (!show_keycodes && (key >= KEY_ESC && key < KEY_ZENKAKUHANKAKU)) { + keyname = "***"; + key = -1; + } else { + keyname = libevdev_event_code_get_name(EV_KEY, key); + keyname = keyname ? keyname : "???"; + } + state = libinput_event_tablet_pad_get_key_state(p); + printq("%s (%d) %s\n", + keyname, + key, + state == LIBINPUT_KEY_STATE_PRESSED ? "pressed" : "released"); +} + + +static void print_switch_event(struct libinput_event *ev) { struct libinput_event_switch *sw = libinput_event_get_switch_event(ev); @@ -879,6 +908,9 @@ handle_and_print_events(struct libinput *li) case LIBINPUT_EVENT_TABLET_PAD_STRIP: print_tablet_pad_strip_event(ev); break; + case LIBINPUT_EVENT_TABLET_PAD_KEY: + print_tablet_pad_key_event(ev); + break; case LIBINPUT_EVENT_SWITCH_TOGGLE: print_switch_event(ev); break; @@ -911,31 +943,36 @@ mainloop(struct libinput *li) fprintf(stderr, "Expected device added events on startup but got none. " "Maybe you don't have the right permissions?\n"); - while (!stop && poll(&fds, 1, -1) > -1) - handle_and_print_events(li); + /* time offset starts with our first received event */ + if (poll(&fds, 1, -1) > -1) { + struct timespec tp; + + clock_gettime(CLOCK_MONOTONIC, &tp); + start_time = tp.tv_sec * 1000 + tp.tv_nsec / 1000000; + do { + handle_and_print_events(li); + } while (!stop && poll(&fds, 1, -1) > -1); + } printf("\n"); } static void usage(void) { - printf("Usage: libinput debug-events [options] [--udev |--device /dev/input/event0]\n"); + printf("Usage: libinput debug-events [options] [--udev |--device /dev/input/event0 ...]\n"); } int main(int argc, char **argv) { struct libinput *li; - struct timespec tp; enum tools_backend backend = BACKEND_NONE; - const char *seat_or_device = "seat0"; + const char *seat_or_devices[60] = {NULL}; + size_t ndevices = 0; bool grab = false; bool verbose = false; struct sigaction act; - clock_gettime(CLOCK_MONOTONIC, &tp); - start_time = tp.tv_sec * 1000 + tp.tv_nsec / 1000000; - tools_init_options(&options); while (1) { @@ -980,12 +1017,25 @@ main(int argc, char **argv) be_quiet = true; break; case OPT_DEVICE: + if (backend == BACKEND_UDEV || + ndevices >= ARRAY_LENGTH(seat_or_devices)) { + usage(); + return EXIT_INVALID_USAGE; + + } backend = BACKEND_DEVICE; - seat_or_device = optarg; + seat_or_devices[ndevices++] = optarg; break; case OPT_UDEV: + if (backend == BACKEND_DEVICE || + ndevices >= ARRAY_LENGTH(seat_or_devices)) { + usage(); + return EXIT_INVALID_USAGE; + + } backend = BACKEND_UDEV; - seat_or_device = optarg; + seat_or_devices[0] = optarg; + ndevices = 1; break; case OPT_GRAB: grab = true; @@ -1004,14 +1054,21 @@ main(int argc, char **argv) } if (optind < argc) { - if (optind < argc - 1 || backend != BACKEND_NONE) { + if (backend == BACKEND_UDEV) { usage(); return EXIT_INVALID_USAGE; } backend = BACKEND_DEVICE; - seat_or_device = argv[optind]; + do { + if (ndevices >= ARRAY_LENGTH(seat_or_devices)) { + usage(); + return EXIT_INVALID_USAGE; + } + seat_or_devices[ndevices++] = argv[optind]; + } while(++optind < argc); } else if (backend == BACKEND_NONE) { backend = BACKEND_UDEV; + seat_or_devices[0] = "seat0"; } memset(&act, 0, sizeof(act)); @@ -1024,7 +1081,10 @@ main(int argc, char **argv) return EXIT_FAILURE; } - li = tools_open_backend(backend, seat_or_device, verbose, &grab); + if (verbose) + printf("libinput version: %s\n", LIBINPUT_VERSION); + + li = tools_open_backend(backend, seat_or_devices, verbose, &grab); if (!li) return EXIT_FAILURE; diff --git a/tools/libinput-debug-events.man b/tools/libinput-debug-events.man index 29a03c5..8edd228 100644 --- a/tools/libinput-debug-events.man +++ b/tools/libinput-debug-events.man @@ -6,7 +6,7 @@ libinput\-debug\-events \- debug helper for libinput .PP .B libinput debug\-events \fI[options]\fB \-\-udev \fI\fB .PP -.B libinput debug\-events \fI[options]\fB [\-\-device] \fI/dev/input/event0\fB +.B libinput debug\-events \fI[options]\fB [\-\-device] \fI/dev/input/event0\fB [\fI/dev/input/event1\fB...] .SH DESCRIPTION .PP The @@ -21,7 +21,7 @@ This tool usually needs to be run as root to have access to the .SH OPTIONS .TP 8 .B \-\-device \fI/dev/input/event0\fR -Use the given device with the path backend. The \fB\-\-device\fR argument may be +Use the given device(s) with the path backend. The \fB\-\-device\fR argument may be omitted. .TP 8 .B \-\-grab @@ -79,6 +79,9 @@ Enable or disable middle button emulation .B \-\-enable\-dwt|\-\-disable\-dwt Enable or disable disable-while-typing .TP 8 +.B \-\-enable\-scroll-button-lock|\-\-disable\-scroll-button-lock +Enable or disable the scroll button lock +.TP 8 .B \-\-set\-click\-method=[none|clickfinger|buttonareas] Set the desired click method .TP 8 diff --git a/tools/libinput-debug-gui.c b/tools/libinput-debug-gui.c index 629a5cf..3d7df1b 100644 --- a/tools/libinput-debug-gui.c +++ b/tools/libinput-debug-gui.c @@ -24,6 +24,7 @@ #include +#include #include #include #include @@ -41,7 +42,9 @@ #include #include -#include +#include "util-strings.h" +#include "util-macros.h" +#include "util-list.h" #include "shared.h" @@ -136,6 +139,7 @@ struct window { double tilt_x, tilt_y; double rotation; double size_major, size_minor; + bool is_down; /* these are for the delta coordinates, but they're not * deltas, they are converted into abs positions */ @@ -532,6 +536,30 @@ draw_tablet(struct window *w, cairo_t *cr) double x, y; int first, last; size_t mask; + int rx, ry; + + /* pressure/distance bars */ + rx = w->width/2 + 100; + ry = w->height/2 + 50; + cairo_save(cr); + cairo_set_source_rgb(cr, .2, .6, .6); + cairo_rectangle(cr, rx, ry, 20, 100); + cairo_stroke(cr); + + if (w->tool.distance > 0) { + double pos = w->tool.distance * 100; + cairo_rectangle(cr, rx, ry + 100 - pos, 20, 5); + cairo_fill(cr); + } + if (w->tool.pressure > 0) { + double pos = w->tool.pressure * 100; + if (w->tool.is_down) + cairo_rectangle(cr, rx + 25, ry + 95, 5, 5); + cairo_rectangle(cr, rx, ry + 100 - pos, 20, pos); + cairo_fill(cr); + } + cairo_restore(cr); + /* tablet tool, square for prox-in location */ cairo_save(cr); @@ -781,6 +809,8 @@ window_init(struct window *w) list_init(&w->evdev_devices); w->win = gtk_window_new(GTK_WINDOW_TOPLEVEL); + if (getenv("LIBINPUT_RUNNING_TEST_SUITE")) + gtk_window_iconify(GTK_WINDOW(w->win)); gtk_widget_set_events(w->win, 0); gtk_window_set_title(GTK_WINDOW(w->win), "libinput debugging tool"); gtk_window_set_default_size(GTK_WINDOW(w->win), 1024, 768); @@ -1306,9 +1336,11 @@ handle_event_tablet(struct libinput_event *ev, struct window *w) LIBINPUT_TABLET_TOOL_TIP_DOWN) { w->tool.x_down = x; w->tool.y_down = y; + w->tool.is_down = true; } else { w->tool.x_up = x; w->tool.y_up = y; + w->tool.is_down = false; } /* fallthrough */ case LIBINPUT_EVENT_TABLET_TOOL_AXIS: @@ -1448,6 +1480,8 @@ handle_event_libinput(GIOChannel *source, GIOCondition condition, gpointer data) case LIBINPUT_EVENT_TABLET_PAD_STRIP: handle_event_tablet_pad(ev, w); break; + case LIBINPUT_EVENT_TABLET_PAD_KEY: + break; case LIBINPUT_EVENT_SWITCH_TOGGLE: break; } @@ -1489,7 +1523,7 @@ main(int argc, char **argv) struct tools_options options; struct libinput *li; enum tools_backend backend = BACKEND_NONE; - const char *seat_or_device = "seat0"; + const char *seat_or_device[2] = {"seat0", NULL}; bool verbose = false; if (!gtk_init_check(&argc, &argv)) @@ -1532,11 +1566,11 @@ main(int argc, char **argv) break; case OPT_DEVICE: backend = BACKEND_DEVICE; - seat_or_device = optarg; + seat_or_device[0] = optarg; break; case OPT_UDEV: backend = BACKEND_UDEV; - seat_or_device = optarg; + seat_or_device[0] = optarg; break; case OPT_GRAB: w.grab = true; @@ -1560,7 +1594,7 @@ main(int argc, char **argv) return EXIT_INVALID_USAGE; } backend = BACKEND_DEVICE; - seat_or_device = argv[optind]; + seat_or_device[0] = argv[optind]; } else if (backend == BACKEND_NONE) { backend = BACKEND_UDEV; } diff --git a/tools/libinput-debug-gui.man b/tools/libinput-debug-gui.man index 3a1b295..5b03ec4 100644 --- a/tools/libinput-debug-gui.man +++ b/tools/libinput-debug-gui.man @@ -73,11 +73,13 @@ respectively, at the touch point or absolute position. .B Tablet tools Events from tablet tools show a cyan square at the proximity-in and proximity-out positions. The tool position is shown as circle and increases -in radius with increasing pressure or distance. Where tilt is available, the -circle changes to an ellipsis to indicate the tilt angle. Relative events -from the tablet tool are displayed as a yellow snake, always starting from -the center of the window on proximity in. Button events are displayed in the -bottom-most button oblong, with the name of the button displayed on press. +in radius with increasing pressure or distance. Pressure and distance are +also shown in the vertical bar south-east of center. Where tilt is +available, the circle changes to an ellipsis to indicate the tilt angle. +Relative events from the tablet tool are displayed as a yellow snake, always +starting from the center of the window on proximity in. Button events are +displayed in the bottom-most button oblong, with the name of the button +displayed on press. .TP 8 .B Tablet pads Button events are displayed in the bottom-most button oblong, with the name diff --git a/tools/libinput-debug-tablet.c b/tools/libinput-debug-tablet.c new file mode 100644 index 0000000..642f1e2 --- /dev/null +++ b/tools/libinput-debug-tablet.c @@ -0,0 +1,596 @@ +/* + * Copyright © 2019 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "shared.h" +#include "util-macros.h" + +static volatile sig_atomic_t stop = 0; +static struct tools_options options; +static int termwidth = 78; + +struct context { + struct libinput *libinput; + struct libinput_device *device; + struct libinput_tablet_tool *tool; + struct libevdev *evdev; + + /* fd[0] ... libinput fd + fd[1] ... libevdev fd */ + struct pollfd fds[2]; + + /* libinput device state */ + bool tip_is_down; + double x, y; + double x_norm, y_norm; + double tx, ty; + double dist, pressure; + double rotation, slider; + unsigned int buttons_down[32]; + unsigned int evdev_buttons_down[32]; + + /* libevdev device state */ + struct { + int x, y, z; + int tilt_x, tilt_y; + int distance, pressure; + } abs; +}; + +static void +print_line(const char *format, ...) +{ + char empty[] = " "; + const int width = 80; + int n; + va_list args; + + printf("\r"); + + va_start(args, format); + n = vprintf(format, args); + va_end(args); + printf("%.*s\n", width - n, empty); +} + +static void +print_buttons(struct context *ctx, unsigned int *buttons, size_t sz) +{ + char buf[256] = {0}; + size_t len = 0; + + for (size_t i = 0; i < sz; i++) { + const char *name; + + if (buttons[i] == 0) + continue; + + name = libevdev_event_code_get_name(EV_KEY, buttons[i]); + len += snprintf(&buf[len], sizeof(buf) - len, "%s ", name); + } + print_line(" buttons: %s", buf); +} + +static void +print_bar(const char *header, double value, double normalized) +{ + char empty[termwidth]; + bool oob = false; + /* the bar is minimum 10 chars, otherwise 78 or whatever fits. + 32 is the manually-added up length of the prefix + [|] */ + const int width = max(10, min(78, termwidth - 32)); + int left_pad, right_pad; + + memset(empty, '-', sizeof empty); + + if (normalized < 0.0 || normalized > 1.0) { + normalized = min(max(normalized, 0.0), 1.0); + oob = true; + } + + left_pad = width * normalized + 0.5; + right_pad = width - left_pad; + + printf("\r %s%-16s %8.2f [%.*s|%.*s]%s\n", + oob ? ANSI_RED : "", + header, + value, + left_pad, empty, + right_pad, empty, + oob ? ANSI_NORMAL : ""); +} + +static double +normalize(struct libevdev *evdev, int code, int value) +{ + const struct input_absinfo *abs; + + if (!evdev) + return 0.0; + + abs = libevdev_get_abs_info(evdev, code); + + if (!abs) + return 0.0; + + return 1.0 * (value - abs->minimum)/(abs->maximum - abs->minimum + 1); +} + +static int +print_state(struct context *ctx) +{ + const char *tool_str; + double w, h; + int lines_printed = 0; + + if (!ctx->device) { + print_line(ANSI_RED "No device connected" ANSI_NORMAL); + lines_printed++; + } else { + libinput_device_get_size(ctx->device, &w, &h); + print_line("Device: %s (%s)", + libinput_device_get_name(ctx->device), + libinput_device_get_sysname(ctx->device)); + lines_printed++; + } + + if (!ctx->tool) { + print_line(ANSI_RED "No tool in proximity " ANSI_NORMAL); + lines_printed++; + } else { + switch (libinput_tablet_tool_get_type(ctx->tool)) { + case LIBINPUT_TABLET_TOOL_TYPE_PEN: + tool_str = "pen"; + break; + case LIBINPUT_TABLET_TOOL_TYPE_ERASER: + tool_str = "eraser"; + break; + case LIBINPUT_TABLET_TOOL_TYPE_BRUSH: + tool_str = "brush"; + break; + case LIBINPUT_TABLET_TOOL_TYPE_PENCIL: + tool_str = "pencil"; + break; + case LIBINPUT_TABLET_TOOL_TYPE_AIRBRUSH: + tool_str = "airbrush"; + break; + case LIBINPUT_TABLET_TOOL_TYPE_MOUSE: + tool_str = "mouse"; + break; + case LIBINPUT_TABLET_TOOL_TYPE_LENS: + tool_str = "lens"; + break; + case LIBINPUT_TABLET_TOOL_TYPE_TOTEM: + tool_str = "totem"; + break; + default: + abort(); + } + + printf("\rTool: %s serial %#" PRIx64 ", id %#" PRIx64 "\n", + tool_str, + libinput_tablet_tool_get_serial(ctx->tool), + libinput_tablet_tool_get_tool_id(ctx->tool)); + lines_printed++; + } + printf("libinput:\n"); + print_line("tip: %s", ctx->tip_is_down ? "down" : "up"); + print_bar("x:", ctx->x, ctx->x_norm); + print_bar("y:", ctx->y, ctx->y_norm); + print_bar("tilt x:", ctx->tx, (ctx->tx + 90)/180); + print_bar("tilt y:", ctx->ty, (ctx->ty + 90)/180); + print_bar("dist:", ctx->dist, ctx->dist); + print_bar("pressure:", ctx->pressure, ctx->pressure); + print_bar("rotation:", ctx->rotation, ctx->rotation/360.0); + print_bar("slider:", ctx->slider, (ctx->slider + 1.0)/2.0); + print_buttons(ctx, + ctx->buttons_down, + ARRAY_LENGTH(ctx->buttons_down)); + lines_printed += 11; + + printf("evdev:\n"); + print_bar("ABS_X:", ctx->abs.x, normalize(ctx->evdev, ABS_X, ctx->abs.x)); + print_bar("ABS_Y:", ctx->abs.y, normalize(ctx->evdev, ABS_Y, ctx->abs.y)); + print_bar("ABS_Z:", ctx->abs.z, normalize(ctx->evdev, ABS_Z, ctx->abs.z)); + print_bar("ABS_TILT_X:", ctx->abs.tilt_x, normalize(ctx->evdev, ABS_TILT_X, ctx->abs.tilt_x)); + print_bar("ABS_TILT_Y:", ctx->abs.tilt_y, normalize(ctx->evdev, ABS_TILT_Y, ctx->abs.tilt_y)); + print_bar("ABS_DISTANCE:", ctx->abs.distance, normalize(ctx->evdev, ABS_DISTANCE, ctx->abs.distance)); + print_bar("ABS_PRESSURE:", ctx->abs.pressure, normalize(ctx->evdev, ABS_PRESSURE, ctx->abs.pressure)); + print_buttons(ctx, + ctx->evdev_buttons_down, + ARRAY_LENGTH(ctx->evdev_buttons_down)); + lines_printed += 9; + + return lines_printed; +} + +static void +handle_device_added(struct context *ctx, struct libinput_event *ev) +{ + struct libinput_device *device = libinput_event_get_device(ev); + struct udev_device *udev_device; + const char *devnode; + + if (ctx->device) + return; + + if (!libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_TABLET_TOOL)) + return; + + ctx->device = libinput_device_ref(device); + + udev_device = libinput_device_get_udev_device(device); + if (!udev_device) + return; + + devnode = udev_device_get_devnode(udev_device); + if (devnode) { + int fd = open(devnode, O_RDONLY|O_NONBLOCK); + assert(fd != -1); + assert(libevdev_new_from_fd(fd, &ctx->evdev) == 0); + } + + udev_device_unref(udev_device); +} + +static void +handle_device_removed(struct context *ctx, struct libinput_event *ev) +{ + struct libinput_device *device = libinput_event_get_device(ev); + + if (ctx->device != device) + return; + + libinput_device_unref(ctx->device); + ctx->device = NULL; + + libevdev_free(ctx->evdev); + ctx->evdev = NULL; + + close(ctx->fds[1].fd); + ctx->fds[1].fd = -1; +} + +static void +update_tablet_axes(struct context *ctx, struct libinput_event_tablet_tool *t) +{ + ctx->x = libinput_event_tablet_tool_get_x(t); + ctx->y = libinput_event_tablet_tool_get_y(t); + ctx->x_norm = libinput_event_tablet_tool_get_x_transformed(t, 1.0); + ctx->y_norm = libinput_event_tablet_tool_get_y_transformed(t, 1.0); + ctx->tx = libinput_event_tablet_tool_get_tilt_x(t); + ctx->ty = libinput_event_tablet_tool_get_tilt_y(t); + ctx->dist = libinput_event_tablet_tool_get_distance(t); + ctx->pressure = libinput_event_tablet_tool_get_pressure(t); + ctx->rotation = libinput_event_tablet_tool_get_rotation(t); + ctx->slider = libinput_event_tablet_tool_get_slider_position(t); +} + +static void +handle_tablet_button_event(struct context *ctx, struct libinput_event *ev) +{ + struct libinput_event_tablet_tool *t = libinput_event_get_tablet_tool_event(ev); + unsigned int button = libinput_event_tablet_tool_get_button(t); + enum libinput_button_state state = libinput_event_tablet_tool_get_button_state(t); + + for (size_t i = 0; i < ARRAY_LENGTH(ctx->buttons_down); i++) { + if (state == LIBINPUT_BUTTON_STATE_PRESSED) { + if (ctx->buttons_down[i] == 0) { + ctx->buttons_down[i] = button; + break; + } + } else { + if (ctx->buttons_down[i] == button) { + ctx->buttons_down[i] = 0; + break; + } + } + } +} + +static void +handle_tablet_axis_event(struct context *ctx, struct libinput_event *ev) +{ + struct libinput_event_tablet_tool *t = libinput_event_get_tablet_tool_event(ev); + + update_tablet_axes(ctx, t); +} + +static void +handle_tablet_proximity_event(struct context *ctx, struct libinput_event *ev) +{ + struct libinput_event_tablet_tool *t = libinput_event_get_tablet_tool_event(ev); + struct libinput_tablet_tool *tool = libinput_event_tablet_tool_get_tool(t); + + if (ctx->tool) { + libinput_tablet_tool_unref(ctx->tool); + ctx->tool = NULL; + } + + if (libinput_event_tablet_tool_get_proximity_state(t) == LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN) + ctx->tool = libinput_tablet_tool_ref(tool); +} + +static void +handle_tablet_tip_event(struct context *ctx, struct libinput_event *ev) +{ + struct libinput_event_tablet_tool *t = libinput_event_get_tablet_tool_event(ev); + + ctx->tip_is_down = libinput_event_tablet_tool_get_tip_state(t) == LIBINPUT_TABLET_TOOL_TIP_DOWN; +} + +static void +handle_libinput_events(struct context *ctx) +{ + struct libinput *li = ctx->libinput; + struct libinput_event *ev; + + libinput_dispatch(li); + while ((ev = libinput_get_event(li))) { + switch (libinput_event_get_type(ev)) { + case LIBINPUT_EVENT_NONE: + abort(); + case LIBINPUT_EVENT_DEVICE_ADDED: + handle_device_added(ctx, ev); + tools_device_apply_config(libinput_event_get_device(ev), + &options); + break; + case LIBINPUT_EVENT_DEVICE_REMOVED: + handle_device_removed(ctx, ev); + break; + case LIBINPUT_EVENT_TABLET_TOOL_BUTTON: + handle_tablet_button_event(ctx, ev); + break; + case LIBINPUT_EVENT_TABLET_TOOL_AXIS: + handle_tablet_axis_event(ctx, ev); + break; + case LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY: + handle_tablet_proximity_event(ctx, ev); + break; + case LIBINPUT_EVENT_TABLET_TOOL_TIP: + handle_tablet_tip_event(ctx, ev); + break; + default: + break; + } + + libinput_event_destroy(ev); + libinput_dispatch(li); + } +} + +static void +handle_libevdev_events(struct context *ctx) +{ + struct libevdev *evdev = ctx->evdev; + struct input_event event; + +#define evbit(_t, _c) (((_t) << 16) | (_c)) + + if (!evdev) + return; + + while (libevdev_next_event(evdev, LIBEVDEV_READ_FLAG_NORMAL, &event) + == LIBEVDEV_READ_STATUS_SUCCESS) + { + switch(evbit(event.type, event.code)) { + case evbit(EV_KEY, BTN_TOOL_PEN): + case evbit(EV_KEY, BTN_TOOL_RUBBER): + case evbit(EV_KEY, BTN_TOOL_BRUSH): + case evbit(EV_KEY, BTN_TOOL_PENCIL): + case evbit(EV_KEY, BTN_TOOL_AIRBRUSH): + case evbit(EV_KEY, BTN_TOOL_MOUSE): + case evbit(EV_KEY, BTN_TOOL_LENS): + ctx->evdev_buttons_down[event.code - BTN_TOOL_PEN] = event.value ? event.code : 0; + break; + /* above tools should be mutually exclusive but let's leave + * enough space */ + case evbit(EV_KEY, BTN_TOUCH): + ctx->evdev_buttons_down[7] = event.value ? event.code : 0; + break; + case evbit(EV_KEY, BTN_STYLUS): + ctx->evdev_buttons_down[8] = event.value ? event.code : 0; + break; + case evbit(EV_KEY, BTN_STYLUS2): + ctx->evdev_buttons_down[9] = event.value ? event.code : 0; + break; + case evbit(EV_KEY, BTN_STYLUS3): + ctx->evdev_buttons_down[10] = event.value ? event.code : 0; + break; + case evbit(EV_ABS, ABS_X): + ctx->abs.x = event.value; + break; + case evbit(EV_ABS, ABS_Y): + ctx->abs.y = event.value; + break; + case evbit(EV_ABS, ABS_Z): + ctx->abs.z = event.value; + break; + case evbit(EV_ABS, ABS_PRESSURE): + ctx->abs.pressure = event.value; + break; + case evbit(EV_ABS, ABS_TILT_X): + ctx->abs.tilt_x = event.value; + break; + case evbit(EV_ABS, ABS_TILT_Y): + ctx->abs.tilt_y = event.value; + break; + case evbit(EV_ABS, ABS_DISTANCE): + ctx->abs.distance = event.value; + break; + } + } +} + +static void +sighandler(int signal, siginfo_t *siginfo, void *userdata) +{ + stop = 1; +} + +static void +mainloop(struct context *ctx) +{ + unsigned int lines_printed = 20; + + ctx->fds[0].fd = libinput_get_fd(ctx->libinput); + + /* pre-load the lines */ + for (unsigned int i = 0; i < lines_printed; i++) + printf("\n"); + + do { + handle_libinput_events(ctx); + handle_libevdev_events(ctx); + + printf(ANSI_LEFT, 1000); + printf(ANSI_UP, lines_printed); + lines_printed = print_state(ctx); + } while (!stop && poll(ctx->fds, 2, -1) > -1); + + printf("\n"); +} + +static void +usage(void) { + printf("Usage: libinput debug-tablet [options] [--udev |--device /dev/input/event0]\n"); +} + +static void +init_context(struct context *ctx) +{ + + memset(ctx, 0, sizeof *ctx); + + ctx->fds[0].fd = -1; /* libinput fd */ + ctx->fds[0].events = POLLIN; + ctx->fds[0].revents = 0; + ctx->fds[1].fd = -1; /* libevdev fd */ + ctx->fds[1].events = POLLIN; + ctx->fds[1].revents = 0; +} + +int +main(int argc, char **argv) +{ + struct context ctx; + struct libinput *li; + enum tools_backend backend = BACKEND_NONE; + const char *seat_or_device[2] = {"seat0", NULL}; + struct sigaction act; + bool grab = false; + + init_context(&ctx); + + tools_init_options(&options); + + while (1) { + int c; + int option_index = 0; + enum { + OPT_DEVICE = 1, + OPT_UDEV, + }; + static struct option opts[] = { + CONFIGURATION_OPTIONS, + { "help", no_argument, 0, 'h' }, + { "device", required_argument, 0, OPT_DEVICE }, + { "udev", required_argument, 0, OPT_UDEV }, + { 0, 0, 0, 0} + }; + + c = getopt_long(argc, argv, "h", opts, &option_index); + if (c == -1) + break; + + switch(c) { + case '?': + exit(EXIT_INVALID_USAGE); + break; + case 'h': + usage(); + exit(EXIT_SUCCESS); + break; + case OPT_DEVICE: + backend = BACKEND_DEVICE; + seat_or_device[0] = optarg; + break; + case OPT_UDEV: + backend = BACKEND_UDEV; + seat_or_device[0] = optarg; + break; + } + + } + + if (optind < argc) { + if (optind < argc - 1 || backend != BACKEND_NONE) { + usage(); + return EXIT_INVALID_USAGE; + } + backend = BACKEND_DEVICE; + seat_or_device[0] = argv[optind]; + } else if (backend == BACKEND_NONE) { + backend = BACKEND_UDEV; + } + + memset(&act, 0, sizeof(act)); + act.sa_sigaction = sighandler; + act.sa_flags = SA_SIGINFO; + + if (sigaction(SIGINT, &act, NULL) == -1) { + fprintf(stderr, "Failed to set up signal handling (%s)\n", + strerror(errno)); + return EXIT_FAILURE; + } + + li = tools_open_backend(backend, seat_or_device, false, &grab); + if (!li) + return EXIT_FAILURE; + + struct winsize w; + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) != -1) + termwidth = w.ws_col; + + ctx.libinput = li; + mainloop(&ctx); + + libinput_unref(li); + + return EXIT_SUCCESS; +} diff --git a/tools/libinput-debug-tablet.man b/tools/libinput-debug-tablet.man new file mode 100644 index 0000000..bfd7573 --- /dev/null +++ b/tools/libinput-debug-tablet.man @@ -0,0 +1,33 @@ +.TH libinput-debug-tablet "1" +.SH NAME +libinput\-debug\-tablet\ \- debug and visualize tablet axes +.SH SYNOPSIS +.B libinput debug-tablet [\-\-help] [options] [\fI/dev/input/event0\fI] +.SH DESCRIPTION +.PP +The +.B "libinput debug-tablet" +tool debugs the values of the various axes on a tablet. This is +an interactive tool. When executed, the tool will prompt the user to +interact with the tablet and display the current value on each available +axis. +.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 +If a device node is given, this tool opens that device node. Otherwise, this +tool searches for the first node that looks like a tablet and uses that +node. +.TP 8 +.B \-\-help +Print help +.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. +.SH LIBINPUT +Part of the +.B libinput(1) +suite diff --git a/tools/libinput-list-devices.c b/tools/libinput-list-devices.c index 4b06452..a803e7b 100644 --- a/tools/libinput-list-devices.c +++ b/tools/libinput-list-devices.c @@ -31,8 +31,8 @@ #include #include -#include #include +#include "util-strings.h" #include "shared.h" @@ -376,6 +376,7 @@ main(int argc, char **argv) struct libinput *li; struct libinput_event *ev; bool grab = false; + const char *seat[2] = {"seat0", NULL}; /* This is kept for backwards-compatibility with the old libinput-list-devices */ @@ -392,7 +393,7 @@ main(int argc, char **argv) } } - li = tools_open_backend(BACKEND_UDEV, "seat0", false, &grab); + li = tools_open_backend(BACKEND_UDEV, seat, false, &grab); if (!li) return 1; diff --git a/tools/libinput-list-devices.man b/tools/libinput-list-devices.man index ccb30ff..7f18622 100644 --- a/tools/libinput-list-devices.man +++ b/tools/libinput-list-devices.man @@ -1,6 +1,7 @@ .TH libinput-list-devices "1" "" "libinput @LIBINPUT_VERSION@" "libinput Manual" .SH NAME -libinput\-list\-devices \- list local devices as recognized by libinput +libinput\-list\-devices \- list local devices as recognized by libinput and +default values of their configuration .SH SYNOPSIS .B libinput list\-devices [\-\-help] .SH DESCRIPTION @@ -11,9 +12,8 @@ tool creates a libinput context on the default seat "seat0" and lists all devices recognized 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 (*). +For options that allow more settings than "enabled/disabled", all available ones +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. diff --git a/tools/libinput-measure-fuzz.py b/tools/libinput-measure-fuzz.py index f7ab5b8..c392d74 100755 --- a/tools/libinput-measure-fuzz.py +++ b/tools/libinput-measure-fuzz.py @@ -1,4 +1,4 @@ -#! /usr/libexec/platform-python +#!/usr/bin/env python3 # vim: set expandtab shiftwidth=4: # -*- Mode: python; coding: utf-8; indent-tabs-mode: nil -*- */ # @@ -45,6 +45,7 @@ OVERRIDE_HWDB_FILE = '/etc/udev/hwdb.d/99-touchpad-fuzz-override.hwdb' class tcolors: GREEN = '\033[92m' RED = '\033[91m' + YELLOW = '\033[93m' BOLD = '\033[1m' NORMAL = '\033[0m' @@ -57,6 +58,10 @@ def print_green(msg, **kwargs): print(tcolors.BOLD + tcolors.GREEN + msg + tcolors.NORMAL, **kwargs) +def print_yellow(msg, **kwargs): + print(tcolors.BOLD + tcolors.YELLOW + msg + tcolors.NORMAL, **kwargs) + + def print_red(msg, **kwargs): print(tcolors.BOLD + tcolors.RED + msg + tcolors.NORMAL, **kwargs) @@ -101,10 +106,10 @@ class Device(libevdev.Device): property. Returns None if the property doesn't exist''' axes = { - 0x00: self.udev_device.get('LIBINPUT_FUZZ_00'), - 0x01: self.udev_device.get('LIBINPUT_FUZZ_01'), - 0x35: self.udev_device.get('LIBINPUT_FUZZ_35'), - 0x36: self.udev_device.get('LIBINPUT_FUZZ_36'), + 0x00: self.udev_device.get('LIBINPUT_FUZZ_00'), + 0x01: self.udev_device.get('LIBINPUT_FUZZ_01'), + 0x35: self.udev_device.get('LIBINPUT_FUZZ_35'), + 0x36: self.udev_device.get('LIBINPUT_FUZZ_36'), } if axes[0x35] is not None: @@ -122,10 +127,10 @@ class Device(libevdev.Device): return None if ((xfuzz is not None and yfuzz is None) or - (xfuzz is None and yfuzz is not None)): + (xfuzz is None and yfuzz is not None)): raise InvalidConfigurationError('fuzz should be set for both axes') - return (xfuzz, yfuzz) + return (int(xfuzz), int(yfuzz)) def check_axes(self): ''' @@ -138,12 +143,12 @@ class Device(libevdev.Device): if self.has(libevdev.EV_ABS.ABS_MT_POSITION_X) != self.has(libevdev.EV_ABS.ABS_MT_POSITION_Y): raise InvalidDeviceError('device does not have both multitouch axes') - xfuzz = self.absinfo[libevdev.EV_ABS.ABS_X].fuzz or \ - self.absinfo[libevdev.EV_ABS.ABS_MT_POSITION_X].fuzz - yfuzz = self.absinfo[libevdev.EV_ABS.ABS_Y].fuzz or \ - self.absinfo[libevdev.EV_ABS.ABS_MT_POSITION_Y].fuzz + xfuzz = (self.absinfo[libevdev.EV_ABS.ABS_X].fuzz or + self.absinfo[libevdev.EV_ABS.ABS_MT_POSITION_X].fuzz) + yfuzz = (self.absinfo[libevdev.EV_ABS.ABS_Y].fuzz or + self.absinfo[libevdev.EV_ABS.ABS_MT_POSITION_Y].fuzz) - if xfuzz is 0 and yfuzz is 0: + if xfuzz == 0 and yfuzz == 0: return None return (xfuzz, yfuzz) @@ -169,10 +174,10 @@ def handle_existing_entry(device, fuzz): # If the lines aren't in the same order in the file, it'll be a false # negative. overrides = { - 0x00: device.udev_device.get('EVDEV_ABS_00'), - 0x01: device.udev_device.get('EVDEV_ABS_01'), - 0x35: device.udev_device.get('EVDEV_ABS_35'), - 0x36: device.udev_device.get('EVDEV_ABS_36'), + 0x00: device.udev_device.get('EVDEV_ABS_00'), + 0x01: device.udev_device.get('EVDEV_ABS_01'), + 0x35: device.udev_device.get('EVDEV_ABS_35'), + 0x36: device.udev_device.get('EVDEV_ABS_36'), } has_existing_rules = False @@ -193,8 +198,8 @@ def handle_existing_entry(device, fuzz): template = [' EVDEV_ABS_00={}'.format(overrides[0x00]), ' EVDEV_ABS_01={}'.format(overrides[0x01])] if overrides[0x35] is not None: - template += [' EVDEV_ABS_35={}'.format(overrides[0x35]), - ' EVDEV_ABS_36={}'.format(overrides[0x36])] + template += [' EVDEV_ABS_35={}'.format(overrides[0x35]), + ' EVDEV_ABS_36={}'.format(overrides[0x36])] print('Checking in {}... '.format(OVERRIDE_HWDB_FILE), end='') entry, prefix, lineno = check_file_for_lines(OVERRIDE_HWDB_FILE, template) @@ -269,13 +274,13 @@ def handle_existing_entry(device, fuzz): def reload_and_trigger_udev(device): import time - print('Running udevadm hwdb --update') - subprocess.run(['udevadm', 'hwdb', '--update'], check=True) + print('Running systemd-hwdb update') + subprocess.run(['systemd-hwdb', 'update'], check=True) syspath = device.path.replace('/dev/input/', '/sys/class/input/') - time.sleep(1) + time.sleep(2) print('Running udevadm trigger {}'.format(syspath)) subprocess.run(['udevadm', 'trigger', syspath], check=True) - time.sleep(1) + time.sleep(2) def test_hwdb_entry(device, fuzz): @@ -284,12 +289,24 @@ def test_hwdb_entry(device, fuzz): d = Device(device.path) f = d.check_axes() - if fuzz == f[0] and fuzz == f[1]: - print_green('Success') - return True + if f is not None: + if f == (fuzz, fuzz): + print_yellow('Warning') + print_bold('The hwdb applied to the device but libinput\'s udev ' + 'rules have not picked it up. This should only happen' + 'if libinput is not installed') + return True + else: + print_red('Error') + return False else: - print_red('Error') - return False + f = d.check_property() + if f is not None and f == (fuzz, fuzz): + print_green('Success') + return True + else: + print_red('Error') + return False def check_file_for_lines(path, template): @@ -410,11 +427,11 @@ def write_udev_rule(device, fuzz): def main(args): parser = argparse.ArgumentParser( - description='Print fuzz settings and/or suggest udev rules for the fuzz to be adjusted.' + description='Print fuzz settings and/or suggest udev rules for the fuzz to be adjusted.' ) parser.add_argument('path', metavar='/dev/input/event0', nargs='?', type=str, help='Path to device (optional)') - parser.add_argument('--fuzz', type=int, help='Suggested fuzz') + parser.add_argument('--fuzz', type=int, help='Suggested fuzz') args = parser.parse_args() try: diff --git a/tools/libinput-measure-touch-size.py b/tools/libinput-measure-touch-size.py index 3683f41..5d98bc2 100755 --- a/tools/libinput-measure-touch-size.py +++ b/tools/libinput-measure-touch-size.py @@ -1,4 +1,4 @@ -#! /usr/libexec/platform-python +#!/usr/bin/env python3 # vim: set expandtab shiftwidth=4: # -*- Mode: python; coding: utf-8; indent-tabs-mode: nil -*- */ # @@ -87,9 +87,9 @@ class Touch(object): def __str__(self): s = "Touch: major {:3d}".format(self.major) if self.minor is not None: - s += ", minor {:3d}".format(self.minor) + s += ", minor {:3d}".format(self.minor) if self.orientation is not None: - s += ", orientation {:+3d}".format(self.orientation) + s += ", orientation {:+3d}".format(self.orientation) return s @@ -119,17 +119,15 @@ class TouchSequence(object): self.major_range.update(touch.major) self.minor_range.update(touch.minor) - if touch.major < self.device.up or \ - touch.minor < self.device.up: - self.is_down = False - elif touch.major > self.device.down or \ - touch.minor > self.device.down: + if touch.major < self.device.up or touch.minor < self.device.up: + self.is_down = False + elif touch.major > self.device.down or touch.minor > self.device.down: self.is_down = True self.was_down = True self.is_palm = touch.major > self.device.palm if self.is_palm: - self.was_palm = True + self.was_palm = True self.is_thumb = self.device.thumb != 0 and touch.major > self.device.thumb if self.is_thumb: @@ -147,16 +145,14 @@ class TouchSequence(object): return "{:78s}".format("Sequence: no major/minor values recorded") s = "Sequence: major: [{:3d}..{:3d}] ".format( - self.major_range.min, self.major_range.max + self.major_range.min, self.major_range.max ) if self.device.has_minor: - s += "minor: [{:3d}..{:3d}] ".format( - self.minor_range.min, self.minor_range.max - ) + s += "minor: [{:3d}..{:3d}] ".format(self.minor_range.min, self.minor_range.max) if self.was_down: - s += " down" + s += " down" if self.was_palm: - s += " palm" + s += " palm" if self.was_thumb: s += " thumb" @@ -164,12 +160,10 @@ class TouchSequence(object): def _str_state(self): touch = self.points[-1] - s = "{}, tags: {} {} {}".format( - touch, - "down" if self.is_down else " ", - "palm" if self.is_palm else " ", - "thumb" if self.is_thumb else " " - ) + s = "{}, tags: {} {} {}".format(touch, + "down" if self.is_down else " ", + "palm" if self.is_palm else " ", + "thumb" if self.is_thumb else " ") return s @@ -250,27 +244,27 @@ class Device(libevdev.Device): libevdev.EV_KEY.BTN_TOOL_QUADTAP, libevdev.EV_KEY.BTN_TOOL_QUINTTAP] if event.code in tapcodes and event.value > 0: - print("\rThis tool cannot handle multiple fingers, " - "output will be invalid", file=sys.stderr) + print("\rThis tool cannot handle multiple fingers, " + "output will be invalid", file=sys.stderr) def handle_abs(self, event): if event.matches(libevdev.EV_ABS.ABS_MT_TRACKING_ID): - if event.value > -1: - self.start_new_sequence(event.value) - else: - try: - s = self.current_sequence() - s.finalize() - print("\r{}".format(s)) - except IndexError: - # If the finger was down during start - pass + if event.value > -1: + self.start_new_sequence(event.value) + else: + try: + s = self.current_sequence() + s.finalize() + print("\r{}".format(s)) + except IndexError: + # If the finger was down during start + pass elif event.matches(libevdev.EV_ABS.ABS_MT_TOUCH_MAJOR): - self.touch.major = event.value + self.touch.major = event.value elif event.matches(libevdev.EV_ABS.ABS_MT_TOUCH_MINOR): - self.touch.minor = event.value + self.touch.minor = event.value elif event.matches(libevdev.EV_ABS.ABS_MT_ORIENTATION): - self.touch.orientation = event.value + self.touch.orientation = event.value def handle_syn(self, event): if self.touch.dirty: @@ -310,7 +304,7 @@ def colon_tuple(string): t = tuple([int(x) for x in ts]) if len(t) == 2 and t[0] >= t[1]: return t - except: + except: # noqa pass msg = "{} is not in format N:M (N >= M)".format(string) @@ -318,9 +312,7 @@ def colon_tuple(string): def main(args): - parser = argparse.ArgumentParser( - description="Measure touch size and orientation" - ) + parser = argparse.ArgumentParser(description="Measure touch size and orientation") parser.add_argument('path', metavar='/dev/input/event0', nargs='?', type=str, help='Path to device (optional)') parser.add_argument('--touch-thresholds', metavar='down:up', @@ -345,7 +337,7 @@ def main(args): except (PermissionError, OSError): print("Error: failed to open device") except InvalidDeviceError as e: - print("This device does not have the capabilities for size-based touch detection."); + print("This device does not have the capabilities for size-based touch detection.") print("Details: {}".format(e)) diff --git a/tools/libinput-measure-touchpad-pressure.man b/tools/libinput-measure-touchpad-pressure.man index f985b87..5983e28 100644 --- a/tools/libinput-measure-touchpad-pressure.man +++ b/tools/libinput-measure-touchpad-pressure.man @@ -53,6 +53,11 @@ device-specific pressure values and it is required that Assume a palm threshold of .I N. The threshold has to be in device-specific pressure values. +.TP 8 +.B \-\-thumb\-threshold=\fIN\fR +Assume a thumb threshold of +.I N. +The threshold has to be in device-specific pressure values. .PP If the touch-thresholds or the palm-threshold are not provided, this tool uses the thresholds provided by the device quirks (if any) or the diff --git a/tools/libinput-measure-touchpad-pressure.py b/tools/libinput-measure-touchpad-pressure.py index dfd9b53..a55bad0 100755 --- a/tools/libinput-measure-touchpad-pressure.py +++ b/tools/libinput-measure-touchpad-pressure.py @@ -1,4 +1,4 @@ -#! /usr/libexec/platform-python +#!/usr/bin/env python3 # vim: set expandtab shiftwidth=4: # -*- Mode: python; coding: utf-8; indent-tabs-mode: nil -*- */ # @@ -37,6 +37,51 @@ except ModuleNotFoundError as e: sys.exit(1) +class TableFormatter(object): + ALIGNMENT = 3 + + def __init__(self): + self.colwidths = [] + + @property + def width(self): + return sum(self.colwidths) + 1 + + def headers(self, args): + s = '|' + align = self.ALIGNMENT - 1 # account for | + + for arg in args: + # +2 because we want space left/right of text + w = ((len(arg) + 2 + align) // align) * align + self.colwidths.append(w + 1) + s += ' {:^{width}s} |'.format(arg, width=w - 2) + + return s + + def values(self, args): + s = '|' + for w, arg in zip(self.colwidths, args): + w -= 1 # width includes | separator + if type(arg) == str: + # We want space margins for strings + s += ' {:{width}s} |'.format(arg, width=w - 2) + elif type(arg) == bool: + s += '{:^{width}s}|'.format('x' if arg else ' ', width=w) + else: + s += '{:^{width}d}|'.format(arg, width=w) + + if len(args) < len(self.colwidths): + s += '|'.rjust(self.width - len(s), ' ') + return s + + def separator(self): + return '+' + '-' * (self.width - 2) + '+' + + +fmt = TableFormatter() + + class Range(object): """Class to keep a min/max of a value around""" def __init__(self): @@ -99,12 +144,12 @@ class TouchSequence(object): def avg(self): """Average pressure value of this sequence""" - return int(sum([p.pressure for p in self.points])/len(self.points)) + return int(sum([p.pressure for p in self.points]) / len(self.points)) def median(self): """Median pressure value of this sequence""" ps = sorted([p.pressure for p in self.points]) - idx = int(len(self.points)/2) + idx = int(len(self.points) / 2) return ps[idx] def __str__(self): @@ -112,36 +157,19 @@ class TouchSequence(object): def _str_summary(self): if not self.points: - return "{:78s}".format("Sequence: no pressure values recorded") - - s = "Sequence {} pressure: "\ - "min: {:3d} max: {:3d} avg: {:3d} median: {:3d} tags:" \ - .format( - self.tracking_id, - self.prange.min, - self.prange.max, - self.avg(), - self.median() - ) - if self.was_down: - s += " down" - if self.was_palm: - s += " palm" - if self.was_thumb: - s += " thumb" + return fmt.values([self.tracking_id, False, False, False, False, + 'No pressure values recorded']) + + s = fmt.values([self.tracking_id, self.was_down, True, self.was_palm, + self.was_thumb, self.prange.min, self.prange.max, 0, + self.avg(), self.median()]) return s def _str_state(self): - s = "Touchpad pressure: {:3d} min: {:3d} max: {:3d} tags: {} {} {}" \ - .format( - self.points[-1].pressure, - self.prange.min, - self.prange.max, - "down" if self.is_down else " ", - "palm" if self.is_palm else " ", - "thumb" if self.is_thumb else " " - ) + s = fmt.values([self.tracking_id, self.is_down, not self.is_down, + self.is_palm, self.is_thumb, self.prange.min, + self.prange.max, self.points[-1].pressure]) return s @@ -227,8 +255,8 @@ def handle_key(device, event): libevdev.EV_KEY.BTN_TOOL_QUINTTAP ] if event.code in tapcodes and event.value > 0: - print("\rThis tool cannot handle multiple fingers, " - "output will be invalid", file=sys.stderr) + print('\r\033[2KThis tool cannot handle multiple fingers, ' + 'output will be invalid') def handle_abs(device, event): @@ -239,7 +267,7 @@ def handle_abs(device, event): try: s = device.current_sequence() s.finalize() - print("\r{}".format(s)) + print("\r\033[2K{}".format(s)) except IndexError: # If the finger was down at startup pass @@ -248,7 +276,7 @@ def handle_abs(device, event): try: s = device.current_sequence() s.append(Touch(pressure=event.value)) - print("\r{}".format(s), end="") + print("\r\033[2K{}".format(s), end="") except IndexError: # If the finger was down at startup pass @@ -262,12 +290,27 @@ def handle_event(device, event): def loop(device): - print("Ready for recording data.") - print("Pressure range used: {}:{}".format(device.down, device.up)) - print("Palm pressure range used: {}".format(device.palm)) - print("Thumb pressure range used: {}".format(device.thumb)) - print("Place a single finger on the touchpad to measure pressure values.\n" - "Ctrl+C to exit\n") + print('This is an interactive tool') + print() + print("Place a single finger on the touchpad to measure pressure values.") + print('Check that:') + print('- touches subjectively perceived as down are tagged as down') + print('- touches with a thumb are tagged as thumb') + print('- touches with a palm are tagged as palm') + print() + print('If the touch states do not match the interaction, re-run') + print('with --touch-thresholds=down:up using observed pressure values.') + print('See --help for more options.') + print() + print("Press Ctrl+C to exit") + print() + + headers = fmt.headers(['Touch', 'down', 'up', 'palm', 'thumb', 'min', 'max', 'p', 'avg', 'median']) + print(fmt.separator()) + print(fmt.values(['Thresh', device.down, device.up, device.palm, device.thumb])) + print(fmt.separator()) + print(headers) + print(fmt.separator()) while True: for event in device.events(): @@ -280,7 +323,7 @@ def colon_tuple(string): t = tuple([int(x) for x in ts]) if len(t) == 2 and t[0] >= t[1]: return t - except: + except: # noqa pass msg = "{} is not in format N:M (N >= M)".format(string) @@ -303,6 +346,10 @@ def main(args): '--palm-threshold', metavar='t', type=int, help='Threshold when a touch is a palm' ) + parser.add_argument( + '--thumb-threshold', metavar='t', type=int, + help='Threshold when a touch is a thumb' + ) args = parser.parse_args() try: @@ -314,13 +361,18 @@ def main(args): if args.palm_threshold is not None: device.palm = args.palm_threshold + if args.thumb_threshold is not None: + device.thumb = args.thumb_threshold + loop(device) except KeyboardInterrupt: - pass + print('\r\033[2K{}'.format(fmt.separator())) + print() + except (PermissionError, OSError): print("Error: failed to open device") except InvalidDeviceError as e: - print("This device does not have the capabilities for pressure-based touch detection."); + print("This device does not have the capabilities for pressure-based touch detection.") print("Details: {}".format(e)) diff --git a/tools/libinput-measure-touchpad-size.man b/tools/libinput-measure-touchpad-size.man new file mode 100644 index 0000000..fdc60c8 --- /dev/null +++ b/tools/libinput-measure-touchpad-size.man @@ -0,0 +1,39 @@ +.TH libinput-measure-touchpad-size "1" +.SH NAME +libinput\-measure\-touchpad\-size \- measure the size of a touchpad +.SH SYNOPSIS +.B libinput measure touchpad\-size [\-\-help] WxH [\fI/dev/input/event0\fI] +.SH DESCRIPTION +.PP +The +.B "libinput measure touchpad\-size" +tool measures the size of a touchpad. This is an interactive tool. When +executed, the tool will prompt the user to interact with the touchpad. The +tool records the axis ranges and calculates the size and resolution based on +those. On termination, the tool prints a hwdb entry that can be added to the +.B 60-evdev.hwdb +file provided by system. +.PP +For details see the online documentation here: +.I https://wayland.freedesktop.org/libinput/doc/latest/absolute-coordinate-ranges.html +.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 +This tool must be provided the physical dimensions of the device in mm. +For example, if your touchpad is 100mm wide and 55mm heigh, run this tool as +.B libinput measure touchpad-size 100x55 +.PP +If a device node is given, this tool opens that device node. Otherwise, this +tool searches for the first node that looks like a touchpad and uses that +node. +.TP 8 +.B \-\-help +Print help +.SH LIBINPUT +Part of the +.B libinput(1) +suite diff --git a/tools/libinput-measure-touchpad-size.py b/tools/libinput-measure-touchpad-size.py new file mode 100755 index 0000000..27c618c --- /dev/null +++ b/tools/libinput-measure-touchpad-size.py @@ -0,0 +1,340 @@ +#!/usr/bin/env python3 +# vim: set expandtab shiftwidth=4: +# -*- Mode: python; coding: utf-8; indent-tabs-mode: nil -*- */ +# +# Copyright © 2020 Red Hat, Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice (including the next +# paragraph) shall be included in all copies or substantial portions of the +# Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. + + +import sys +import argparse +try: + import libevdev + import pyudev +except ModuleNotFoundError as e: + print('Error: {}'.format(str(e)), file=sys.stderr) + print('One or more python modules are missing. Please install those ' + 'modules and re-run this tool.') + sys.exit(1) + + +class DeviceError(Exception): + pass + + +class Point: + def __init__(self, x=None, y=None): + self.x = x + self.y = y + + +class Touchpad(object): + def __init__(self, evdev): + x = evdev.absinfo[libevdev.EV_ABS.ABS_X] + y = evdev.absinfo[libevdev.EV_ABS.ABS_Y] + if not x or not y: + raise DeviceError('Device does not have an x or axis') + + if not x.resolution or not y.resolution: + print('Device does not have resolutions.', file=sys.stderr) + x.resolution = 1 + y.resolution = 1 + + self.xrange = (x.maximum - x.minimum) + self.yrange = (y.maximum - y.minimum) + self.width = self.xrange / x.resolution + self.height = self.yrange / y.resolution + + self._x = x + self._y = y + + # We try to make the touchpad at least look proportional. The + # terminal character space is (guesswork) ca 2.3 times as high as + # wide. + self.columns = 30 + self.rows = int(self.columns * (self.yrange // y.resolution) // (self.xrange // x.resolution) / 2.3) + self.pos = Point(0, 0) + self.min = Point() + self.max = Point() + + @property + def x(self): + return self._x + + @property + def y(self): + return self._y + + @x.setter + def x(self, x): + self._x.minimum = min(self.x.minimum, x) + self._x.maximum = max(self.x.maximum, x) + self.min.x = min(x, self.min.x or 0xffffffff) + self.max.x = max(x, self.max.x or -0xffffffff) + # we calculate the position based on the original range. + # this means on devices with a narrower range than advertised, not + # all corners may be reachable in the touchpad drawing. + self.pos.x = min(0.99, (x - self._x.minimum) / self.xrange) + + @y.setter + def y(self, y): + self._y.minimum = min(self.y.minimum, y) + self._y.maximum = max(self.y.maximum, y) + self.min.y = min(y, self.min.y or 0xffffffff) + self.max.y = max(y, self.max.y or -0xffffffff) + # we calculate the position based on the original range. + # this means on devices with a narrower range than advertised, not + # all corners may be reachable in the touchpad drawing. + self.pos.y = min(0.99, (y - self._y.minimum) / self.yrange) + + def update_from_data(self): + if None in [self.min.x, self.min.y, self.max.x, self.max.y]: + raise DeviceError('Insufficient data to continue') + self._x.minimum = self.min.x + self._x.maximum = self.max.x + self._y.minimum = self.min.y + self._y.maximum = self.max.y + + def draw(self): + print('Detected axis range: x [{:4d}..{:4d}], y [{:4d}..{:4d}]'.format( + self.min.x if self.min.x is not None else 0, + self.max.x if self.max.x is not None else 0, + self.min.y if self.min.y is not None else 0, + self.max.y if self.max.y is not None else 0)) + + print() + print('Move one finger along all edges of the touchpad'.center(self.columns)) + print('until the detected axis range stops changing.'.center(self.columns)) + + top = int(self.pos.y * self.rows) + + print('+{}+'.format(''.ljust(self.columns, '-'))) + for row in range(0, top): + print('|{}|'.format(''.ljust(self.columns))) + + left = int(self.pos.x * self.columns) + right = max(0, self.columns - 1 - left) + print('|{}{}{}|'.format( + ''.ljust(left), + 'O', + ''.ljust(right))) + + for row in range(top + 1, self.rows): + print('|{}|'.format(''.ljust(self.columns))) + + print('+{}+'.format(''.ljust(self.columns, '-'))) + + print('Press Ctrl+C to stop'.center(self.columns)) + + print('\033[{}A'.format(self.rows + 8), flush=True) + + self.rows_printed = self.rows + 8 + + def erase(self): + # Erase all previous lines so we're not left with rubbish + for row in range(self.rows_printed): + print('\033[K') + print('\033[{}A'.format(self.rows_printed)) + + +def dimension(string): + try: + ts = string.split('x') + t = tuple([int(x) for x in ts]) + if len(t) == 2: + return t + except: # noqa + pass + + msg = "{} is not in format WxH".format(string) + raise argparse.ArgumentTypeError(msg) + + +def between(v1, v2, deviation): + return v1 - deviation < v2 < v1 + deviation + + +def dmi_modalias_match(modalias): + modalias = modalias.split(':') + dmi = {'svn': None, 'pvr': None, 'pn': None} + for m in modalias: + for key in dmi: + if m.startswith(key): + dmi[key] = m[len(key):] + + # Based on the current 60-evdev.hwdb, Lenovo uses pvr and everyone else + # uses pn to provide a human-identifiable match + if dmi['svn'] == 'LENOVO': + return 'dmi:*svn{}:*pvr{}*'.format(dmi['svn'], dmi['pvr']) + else: + return 'dmi:*svn{}:*pn{}*'.format(dmi['svn'], dmi['pn']) + + +def main(args): + parser = argparse.ArgumentParser( + description="Measure the touchpad size" + ) + parser.add_argument( + 'size', metavar='WxH', type=dimension, + help='Touchpad size (width by height) in mm', + ) + parser.add_argument( + 'path', metavar='/dev/input/event0', nargs='?', type=str, + help='Path to device (optional)' + ) + context = pyudev.Context() + + args = parser.parse_args() + if not args.path: + for device in context.list_devices(subsystem='input'): + if (device.get('ID_INPUT_TOUCHPAD', 0) and + (device.device_node or '').startswith('/dev/input/event')): + args.path = device.device_node + name = 'unknown' + parent = device + while parent is not None: + n = parent.get('NAME', None) + if n: + name = n + break + parent = parent.parent + + print('Using {}: {}'.format(name, device.device_node)) + break + else: + print('Unable to find a touchpad device.', file=sys.stderr) + return 1 + + dev = pyudev.Devices.from_device_file(context, args.path) + overrides = [p for p in dev.properties if p.startswith('EVDEV_ABS')] + if overrides: + print() + print('********************************************************************') + print('WARNING: axis overrides already in place for this device:') + for prop in overrides: + print(' {}={}'.format(prop, dev.properties[prop])) + print('The systemd hwdb already overrides the axis ranges and/or resolution.') + print('This tool is not needed unless you want to verify the axis overrides.') + print('********************************************************************') + print() + + try: + fd = open(args.path, 'rb') + evdev = libevdev.Device(fd) + touchpad = Touchpad(evdev) + print('Kernel specified touchpad size: {:.1f}x{:.1f}mm'.format(touchpad.width, touchpad.height)) + print('User specified touchpad size: {:.1f}x{:.1f}mm'.format(*args.size)) + + print() + print('Kernel axis range: x [{:4d}..{:4d}], y [{:4d}..{:4d}]'.format( + touchpad.x.minimum, touchpad.x.maximum, + touchpad.y.minimum, touchpad.y.maximum)) + + print('Put your finger on the touchpad to start\033[1A') + + try: + touchpad.draw() + while True: + for event in evdev.events(): + if event.matches(libevdev.EV_ABS.ABS_X): + touchpad.x = event.value + elif event.matches(libevdev.EV_ABS.ABS_Y): + touchpad.y = event.value + elif event.matches(libevdev.EV_SYN.SYN_REPORT): + touchpad.draw() + except KeyboardInterrupt: + touchpad.erase() + touchpad.update_from_data() + + print('Detected axis range: x [{:4d}..{:4d}], y [{:4d}..{:4d}]'.format( + touchpad.x.minimum, touchpad.x.maximum, + touchpad.y.minimum, touchpad.y.maximum)) + + touchpad.x.resolution = round((touchpad.x.maximum - touchpad.x.minimum) / args.size[0]) + touchpad.y.resolution = round((touchpad.y.maximum - touchpad.y.minimum) / args.size[1]) + + print('Resolutions calculated based on user-specified size: x {}, y {} units/mm'.format( + touchpad.x.resolution, touchpad.y.resolution)) + + # If both x/y are within some acceptable deviation, we skip the axis + # overrides and only override the resolution + xorig = evdev.absinfo[libevdev.EV_ABS.ABS_X] + yorig = evdev.absinfo[libevdev.EV_ABS.ABS_Y] + deviation = 1.5 * touchpad.x.resolution # 1.5 mm rounding on each side + skip = between(xorig.minimum, touchpad.x.minimum, deviation) + skip = skip and between(xorig.maximum, touchpad.x.maximum, deviation) + deviation = 1.5 * touchpad.y.resolution # 1.5 mm rounding on each side + skip = skip and between(yorig.minimum, touchpad.y.minimum, deviation) + skip = skip and between(yorig.maximum, touchpad.y.maximum, deviation) + + if skip: + print() + print('Note: Axis ranges within acceptable deviation, skipping min/max override') + print() + + print() + print('Suggested hwdb entry:') + + use_dmi = evdev.id['bustype'] not in [0x03, 0x05] # USB, Bluetooth + if use_dmi: + modalias = open('/sys/class/dmi/id/modalias').read().strip() + print('Note: the dmi modalias match is a guess based on your machine\'s modalias:') + print(' ', modalias) + print('Please verify that this is the most sensible match and adjust if necessary.') + + print('-8<--------------------------') + print('# Laptop model description (e.g. Lenovo X1 Carbon 5th)') + if use_dmi: + print('evdev:name:{}:{}*'.format(evdev.name, dmi_modalias_match(modalias))) + else: + print('evdev:input:b{:04X}v{:04X}p{:04X}*'.format( + evdev.id['bustype'], evdev.id['vendor'], evdev.id['product'])) + print(' EVDEV_ABS_00={}:{}:{}'.format( + touchpad.x.minimum if not skip else '', + touchpad.x.maximum if not skip else '', + touchpad.x.resolution)) + print(' EVDEV_ABS_01={}:{}:{}'.format( + touchpad.y.minimum if not skip else '', + touchpad.y.maximum if not skip else '', + touchpad.y.resolution)) + if evdev.absinfo[libevdev.EV_ABS.ABS_MT_POSITION_X]: + print(' EVDEV_ABS_35={}:{}:{}'.format( + touchpad.x.minimum if not skip else '', + touchpad.x.maximum if not skip else '', + touchpad.x.resolution)) + print(' EVDEV_ABS_36={}:{}:{}'.format( + touchpad.y.minimum if not skip else '', + touchpad.y.maximum if not skip else '', + touchpad.y.resolution)) + print('-8<--------------------------') + print('Instructions on what to do with this snippet are in /usr/lib/udev/hwdb.d/60-evdev.hwdb') + except DeviceError as e: + print('Error: {}'.format(e), file=sys.stderr) + return 1 + except PermissionError: + print('Unable to open device. Please run me as root', file=sys.stderr) + return 1 + + return 0 + + +if __name__ == "__main__": + sys.exit(main(sys.argv)) diff --git a/tools/libinput-measure-touchpad-tap.py b/tools/libinput-measure-touchpad-tap.py index 92ac139..b42b78e 100755 --- a/tools/libinput-measure-touchpad-tap.py +++ b/tools/libinput-measure-touchpad-tap.py @@ -1,4 +1,4 @@ -#! /usr/libexec/platform-python +#!/usr/bin/env python3 # vim: set expandtab shiftwidth=4: # -*- Mode: python; coding: utf-8; indent-tabs-mode: nil -*- */ # @@ -52,7 +52,7 @@ def tv2us(sec, usec): def us2ms(us): - return int(us/1000) + return int(us / 1000) class Touch(object): @@ -103,7 +103,7 @@ class Device(libevdev.Device): device_node = None for device in context.list_devices(subsystem='input'): if (not device.device_node or - not device.device_node.startswith('/dev/input/event')): + not device.device_node.startswith('/dev/input/event')): continue # pick the touchpad by default, fallback to the first @@ -165,12 +165,12 @@ class Device(libevdev.Device): dmax = max(deltas) dmin = min(deltas) - l = len(deltas) + ndeltas = len(deltas) - davg = sum(deltas)/l - dmedian = deltas[int(l/2)] - d95pc = deltas[int(l * 0.95)] - d90pc = deltas[int(l * 0.90)] + davg = sum(deltas) / ndeltas + dmedian = deltas[int(ndeltas / 2)] + d95pc = deltas[int(ndeltas * 0.95)] + d90pc = deltas[int(ndeltas * 0.90)] print("Time: ") print(" Max delta: {}ms".format(int(dmax))) @@ -220,9 +220,7 @@ class Device(libevdev.Device): def main(args): - parser = argparse.ArgumentParser( - description="Measure tap-to-click properties of devices" - ) + parser = argparse.ArgumentParser(description="Measure tap-to-click properties of devices") parser.add_argument('path', metavar='/dev/input/event0', nargs='?', type=str, help='Path to device (optional)') parser.add_argument('--format', metavar='format', diff --git a/tools/libinput-measure.c b/tools/libinput-measure.c index fb9729c..2ed72a8 100644 --- a/tools/libinput-measure.c +++ b/tools/libinput-measure.c @@ -23,15 +23,8 @@ #include "config.h" -#include #include -#include #include -#include -#include -#include - -#include #include "shared.h" diff --git a/tools/libinput-measure.man b/tools/libinput-measure.man index 9dd0f64..44ec68d 100644 --- a/tools/libinput-measure.man +++ b/tools/libinput-measure.man @@ -8,7 +8,7 @@ libinput\-measure \- measure properties of devices The .B "libinput measure" tool measures properties of one (or more) devices. Depending on what is to -be measured, this too may not create a libinput context. +be measured, this tool may not create a libinput context. .PP This is a debugging tool only, its output may change at any time. Do not rely on the output. @@ -28,7 +28,10 @@ Measure touch fuzz to avoid pointer jitter .B libinput\-measure\-touch\-size(1) Measure touch size and orientation .TP 8 -.B libinput\-measure\-touchpad\-tap\-time(1) +.B libinput\-measure\-touchpad\-size(1) +Measure the size of a touchpad +.TP 8 +.B libinput\-measure\-touchpad\-tap(1) Measure tap-to-click time .TP 8 .B libinput\-measure\-touchpad\-pressure(1) diff --git a/tools/libinput-quirks.c b/tools/libinput-quirks.c index 0579632..1a80f36 100644 --- a/tools/libinput-quirks.c +++ b/tools/libinput-quirks.c @@ -29,7 +29,6 @@ #include #include -#include "libinput-util.h" #include "quirks.h" #include "shared.h" #include "builddir.h" diff --git a/tools/libinput-record-verify-yaml.py b/tools/libinput-record-verify-yaml.py index 4462f64..a40ca5e 100755 --- a/tools/libinput-record-verify-yaml.py +++ b/tools/libinput-record-verify-yaml.py @@ -1,4 +1,4 @@ -#! /usr/libexec/platform-python +#!/usr/bin/env python3 # vim: set expandtab shiftwidth=4: # -*- Mode: python; coding: utf-8; indent-tabs-mode: nil -*- */ # @@ -61,7 +61,7 @@ class TestYaml(unittest.TestCase): for ev in libinput: if (filter is None or ev['type'] == filter or - isinstance(filter, list) and ev['type'] in filter): + isinstance(filter, list) and ev['type'] in filter): yield ev def test_sections_exist(self): @@ -632,7 +632,8 @@ class TestYaml(unittest.TestCase): self.assertTrue(isinstance(wd, 1)) self.assertGreaterEqual(wd, 0.0) - def sign(x): (1, -1)[x < 0] + def sign(x): + (1, -1)[x < 0] self.assertTrue(sign(w), sign(wd)) except KeyError: pass diff --git a/tools/libinput-record.c b/tools/libinput-record.c index 75fa284..79715a9 100644 --- a/tools/libinput-record.c +++ b/tools/libinput-record.c @@ -39,13 +39,17 @@ #include #include #include +#include #include "libinput-versionsort.h" -#include "libinput-util.h" #include "libinput-version.h" #include "libinput-git-version.h" #include "shared.h" #include "builddir.h" +#include "util-list.h" +#include "util-time.h" +#include "util-input-event.h" +#include "util-macros.h" static const int FILE_VERSION_NUMBER = 1; @@ -77,6 +81,8 @@ struct record_device { struct list link; char *devnode; /* device node of the source device */ struct libevdev *evdev; + struct libevdev *evdev_prev; /* previous value, used for EV_ABS + deltas */ struct libinput_device *device; struct event *events; @@ -190,19 +196,29 @@ noiprintf(const struct record_context *ctx, const char *format, ...) assert(rc != -1 && (unsigned int)rc > 0); } +static inline uint64_t +time_offset(struct record_context *ctx, uint64_t time) +{ + return ctx->offset ? time - ctx->offset : 0; +} + static inline void -print_evdev_event(struct record_context *ctx, struct input_event *ev) +print_evdev_event(struct record_context *ctx, + struct record_device *dev, + struct input_event *ev) { - const char *cname; + const char *tname, *cname; bool was_modified = false; char desc[1024]; + uint64_t time = input_event_time(ev) - ctx->offset; - ev->time = us2tv(tv2us(&ev->time) - ctx->offset); + input_event_set_time(ev, time); /* Don't leak passwords unless the user wants to */ if (!ctx->show_keycodes) was_modified = obfuscate_keycode(ev); + tname = libevdev_event_type_get_name(ev->type); cname = libevdev_event_code_get_name(ev->type, ev->code); if (ev->type == EV_SYN && ev->code == SYN_MT_REPORT) { @@ -215,7 +231,7 @@ print_evdev_event(struct record_context *ctx, struct input_event *ev) static unsigned long last_ms = 0; unsigned long time, dt; - time = us2ms(tv2us(&ev->time)); + time = us2ms(input_event_time(ev)); dt = time - last_ms; last_ms = time; @@ -225,9 +241,87 @@ print_evdev_event(struct record_context *ctx, struct input_event *ev) cname, ev->value, dt); - } else { - const char *tname = libevdev_event_type_get_name(ev->type); + } else if (ev->type == EV_ABS) { + int oldval = 0; + enum { DELTA, SLOT_DELTA, NO_DELTA } want = DELTA; + int delta = 0; + + /* We want to print deltas for abs axes but there are a few + * that we don't care about for actual deltas because + * they're meaningless. + * + * Also, any slotted axis needs to be printed per slot + */ + switch (ev->code) { + case ABS_MT_SLOT: + libevdev_set_event_value(dev->evdev_prev, + ev->type, + ev->code, + ev->value); + want = NO_DELTA; + break; + case ABS_MT_TRACKING_ID: + case ABS_MT_BLOB_ID: + want = NO_DELTA; + break; + case ABS_MT_TOUCH_MAJOR ... ABS_MT_POSITION_Y: + case ABS_MT_PRESSURE ... ABS_MT_TOOL_Y: + if (libevdev_get_num_slots(dev->evdev_prev) > 0) + want = SLOT_DELTA; + break; + default: + break; + } + switch (want) { + case DELTA: + oldval = libevdev_get_event_value(dev->evdev_prev, + ev->type, + ev->code); + libevdev_set_event_value(dev->evdev_prev, + ev->type, + ev->code, + ev->value); + break; + case SLOT_DELTA: { + int slot = libevdev_get_current_slot(dev->evdev_prev); + oldval = libevdev_get_slot_value(dev->evdev_prev, + slot, + ev->code); + libevdev_set_slot_value(dev->evdev_prev, + slot, + ev->code, + ev->value); + break; + } + case NO_DELTA: + break; + + } + + delta = ev->value - oldval; + + switch (want) { + case DELTA: + case SLOT_DELTA: + snprintf(desc, + sizeof(desc), + "%s / %-20s %6d (%+d)", + tname, + cname, + ev->value, + delta); + break; + case NO_DELTA: + snprintf(desc, + sizeof(desc), + "%s / %-20s %6d", + tname, + cname, + ev->value); + break; + } + } else { snprintf(desc, sizeof(desc), "%s / %-20s %6d%s", @@ -238,9 +332,9 @@ print_evdev_event(struct record_context *ctx, struct input_event *ev) } iprintf(ctx, - "- [%3lu, %6u, %3d, %3d, %6d] # %s\n", - ev->time.tv_sec, - (unsigned int)ev->time.tv_usec, + "- [%3lu, %6u, %3d, %3d, %7d] # %s\n", + ev->input_event_sec, + (unsigned int)ev->input_event_usec, ev->type, ev->code, ev->value, @@ -268,16 +362,19 @@ handle_evdev_frame(struct record_context *ctx, struct record_device *d) while (libevdev_next_event(evdev, LIBEVDEV_READ_FLAG_NORMAL, &e) == LIBEVDEV_READ_STATUS_SUCCESS) { + uint64_t time = input_event_time(&e); if (ctx->offset == 0) - ctx->offset = tv2us(&e.time); + ctx->offset = time; + else + time = time_offset(ctx, time); if (d->nevents == d->events_sz) resize(d->events, d->events_sz); event = &d->events[d->nevents++]; event->type = EVDEV; - event->time = tv2us(&e.time) - ctx->offset; + event->time = time; event->u.evdev = e; count++; @@ -366,9 +463,7 @@ buffer_key_event(struct record_context *ctx, abort(); } - time = ctx->offset ? - libinput_event_keyboard_get_time_usec(k) - ctx->offset : 0; - + time = time_offset(ctx, libinput_event_keyboard_get_time_usec(k)); state = libinput_event_keyboard_get_key_state(k); key = libinput_event_keyboard_get_key(k); @@ -408,9 +503,7 @@ buffer_motion_event(struct record_context *ctx, abort(); } - time = ctx->offset ? - libinput_event_pointer_get_time_usec(p) - ctx->offset : 0; - + time = time_offset(ctx, libinput_event_pointer_get_time_usec(p)); event->time = time; snprintf(event->u.libinput.msg, sizeof(event->u.libinput.msg), @@ -443,8 +536,7 @@ buffer_absmotion_event(struct record_context *ctx, abort(); } - time = ctx->offset ? - libinput_event_pointer_get_time_usec(p) - ctx->offset : 0; + time = time_offset(ctx, libinput_event_pointer_get_time_usec(p)); event->time = time; snprintf(event->u.libinput.msg, @@ -476,8 +568,7 @@ buffer_pointer_button_event(struct record_context *ctx, abort(); } - time = ctx->offset ? - libinput_event_pointer_get_time_usec(p) - ctx->offset : 0; + time = time_offset(ctx, libinput_event_pointer_get_time_usec(p)); button = libinput_event_pointer_get_button(p); state = libinput_event_pointer_get_button_state(p); @@ -512,8 +603,7 @@ buffer_pointer_axis_event(struct record_context *ctx, abort(); } - time = ctx->offset ? - libinput_event_pointer_get_time_usec(p) - ctx->offset : 0; + time = time_offset(ctx, libinput_event_pointer_get_time_usec(p)); if (libinput_event_pointer_has_axis(p, LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL)) { h = libinput_event_pointer_get_axis_value(p, @@ -583,8 +673,7 @@ buffer_touch_event(struct record_context *ctx, abort(); } - time = ctx->offset ? - libinput_event_touch_get_time_usec(t) - ctx->offset : 0; + time = time_offset(ctx, libinput_event_touch_get_time_usec(t)); if (etype != LIBINPUT_EVENT_TOUCH_FRAME) { slot = libinput_event_touch_get_slot(t); @@ -667,8 +756,7 @@ buffer_gesture_event(struct record_context *ctx, abort(); } - time = ctx->offset ? - libinput_event_gesture_get_time_usec(g) - ctx->offset : 0; + time = time_offset(ctx, libinput_event_gesture_get_time_usec(g)); event->time = time; switch (etype) { @@ -851,10 +939,7 @@ buffer_tablet_tool_proximity_event(struct record_context *ctx, } prox = libinput_event_tablet_tool_get_proximity_state(t); - - time = ctx->offset ? - libinput_event_tablet_tool_get_time_usec(t) - ctx->offset : 0; - + time = time_offset(ctx, libinput_event_tablet_tool_get_time_usec(t)); axes = buffer_tablet_axes(t); idx = 0; @@ -910,9 +995,7 @@ buffer_tablet_tool_button_event(struct record_context *ctx, button = libinput_event_tablet_tool_get_button(t); state = libinput_event_tablet_tool_get_button_state(t); - - time = ctx->offset ? - libinput_event_tablet_tool_get_time_usec(t) - ctx->offset : 0; + time = time_offset(ctx, libinput_event_tablet_tool_get_time_usec(t)); event->time = time; snprintf(event->u.libinput.msg, @@ -965,10 +1048,7 @@ buffer_tablet_tool_event(struct record_context *ctx, } tip = libinput_event_tablet_tool_get_tip_state(t); - - time = ctx->offset ? - libinput_event_tablet_tool_get_time_usec(t) - ctx->offset : 0; - + time = time_offset(ctx, libinput_event_tablet_tool_get_time_usec(t)); axes = buffer_tablet_axes(t); event->time = time; @@ -1005,9 +1085,7 @@ buffer_tablet_pad_button_event(struct record_context *ctx, abort(); } - time = ctx->offset ? - libinput_event_tablet_pad_get_time_usec(p) - ctx->offset : 0; - + time = time_offset(ctx, libinput_event_tablet_pad_get_time_usec(p)); button = libinput_event_tablet_pad_get_button_number(p), state = libinput_event_tablet_pad_get_button_state(p); mode = libinput_event_tablet_pad_get_mode(p); @@ -1075,9 +1153,7 @@ buffer_tablet_pad_ringstrip_event(struct record_context *ctx, abort(); } - time = ctx->offset ? - libinput_event_tablet_pad_get_time_usec(p) - ctx->offset : 0; - + time = time_offset(ctx, libinput_event_tablet_pad_get_time_usec(p)); mode = libinput_event_tablet_pad_get_mode(p); event->time = time; @@ -1112,9 +1188,7 @@ buffer_switch_event(struct record_context *ctx, abort(); } - time = ctx->offset ? - libinput_event_switch_get_time_usec(s) - ctx->offset : 0; - + time = time_offset(ctx, libinput_event_switch_get_time_usec(s)); sw = libinput_event_switch_get_switch(s); state = libinput_event_switch_get_switch_state(s); @@ -1257,7 +1331,7 @@ print_cached_events(struct record_context *ctx, switch (e->type) { case EVDEV: - print_evdev_event(ctx, &e->u.evdev); + print_evdev_event(ctx, d, &e->u.evdev); break; case LIBINPUT: iprintf(ctx, "- %s\n", e->u.libinput.msg); @@ -1355,26 +1429,54 @@ print_system_header(struct record_context *ctx) { struct utsname u; const char *kernel = "unknown"; - FILE *dmi; - char modalias[2048] = "unknown"; + FILE *dmi, *osrelease; + char dmistr[2048] = "unknown"; + + iprintf(ctx, "system:\n"); + indent_push(ctx); + + /* /etc/os-release version and distribution name */ + osrelease = fopen("/etc/os-release", "r"); + if (!osrelease) + osrelease = fopen("/usr/lib/os-release", "r"); + if (osrelease) { + char *distro = NULL, *version = NULL; + char osrstr[256] = "unknown"; + + while (fgets(osrstr, sizeof(osrstr), osrelease)) { + osrstr[strlen(osrstr) - 1] = '\0'; /* linebreak */ + + if (!distro && strneq(osrstr, "ID=", 3)) + distro = strstrip(&osrstr[3], "\"'"); + else if (!version && strneq(osrstr, "VERSION_ID=", 11)) + version = strstrip(&osrstr[11], "\"'"); + + if (distro && version) { + iprintf(ctx, "os: \"%s:%s\"\n", distro, version); + break; + } + } + free(distro); + free(version); + fclose(osrelease); + } + /* kernel version */ if (uname(&u) != -1) kernel = u.release; + iprintf(ctx, "kernel: \"%s\"\n", kernel); + /* dmi modalias */ dmi = fopen("/sys/class/dmi/id/modalias", "r"); if (dmi) { - if (fgets(modalias, sizeof(modalias), dmi)) { - modalias[strlen(modalias) - 1] = '\0'; /* linebreak */ + if (fgets(dmistr, sizeof(dmistr), dmi)) { + dmistr[strlen(dmistr) - 1] = '\0'; /* linebreak */ } else { - sprintf(modalias, "unknown"); + sprintf(dmistr, "unknown"); } fclose(dmi); } - - iprintf(ctx, "system:\n"); - indent_push(ctx); - iprintf(ctx, "kernel: \"%s\"\n", kernel); - iprintf(ctx, "dmi: \"%s\"\n", modalias); + iprintf(ctx, "dmi: \"%s\"\n", dmistr); indent_pop(ctx); } @@ -1631,7 +1733,7 @@ print_hid_report_descriptor(struct record_context *ctx, report_descriptor is available in sysfs and two devices up from our device. 2 digits for the event number should be enough. This approach won't work for /dev/input/by-id devices. */ - if (!strneq(dev->devnode, prefix, strlen(prefix)) || + if (!strstartswith(dev->devnode, prefix) || strlen(dev->devnode) > strlen(prefix) + 2) return; @@ -1694,7 +1796,7 @@ print_udev_properties(struct record_context *ctx, struct record_device *dev) if (strneq(key, "ID_INPUT", 8) || strneq(key, "LIBINPUT", 8) || - strneq(key, "EV_ABS", 6) || + strneq(key, "EVDEV_ABS", 9) || strneq(key, "MOUSE_DPI", 9) || strneq(key, "POINTINGSTICK_", 14)) { value = udev_list_entry_get_value(entry); @@ -2208,6 +2310,8 @@ init_device(struct record_context *ctx, char *path) } rc = libevdev_new_from_fd(fd, &d->evdev); + if (rc == 0) + rc = libevdev_new_from_fd(fd, &d->evdev_prev); if (rc != 0) { fprintf(stderr, "Failed to create context for %s (%s)\n", @@ -2285,7 +2389,7 @@ init_libinput(struct record_context *ctx) static inline void usage(void) { - printf("Usage: %s [--help] [--multiple|--all] [--autorestart] [--output-file filename] [/dev/input/event0] [...]\n" + printf("Usage: %s [--help] [--all] [--autorestart] [--output-file filename] [/dev/input/event0] [...]\n" "Common use-cases:\n" "\n" " sudo %s -o recording.yml\n" @@ -2296,7 +2400,7 @@ usage(void) " As above, but restarts after 2s of inactivity on the device.\n" " Note, the output file is only the prefix.\n" "\n" - " sudo %s --multiple -o recording.yml /dev/input/event3 /dev/input/event4\n" + " sudo %s -o recording.yml /dev/input/event3 /dev/input/event4\n" " Records the two devices into the same recordings file.\n" "\n" "For more information, see the %s(1) man page\n", @@ -2307,6 +2411,28 @@ usage(void) program_invocation_short_name); } +enum ftype { + F_FILE = 8, + F_DEVICE, + F_NOEXIST, +}; + +static inline enum ftype is_char_dev(const char *path) +{ + struct stat st; + + if (strneq(path, "/dev", 4)) + return F_DEVICE; + + if (stat(path, &st) != 0) { + if (errno == ENOENT) + return F_NOEXIST; + return F_FILE; + } + + return S_ISCHR(st.st_mode) ? F_DEVICE : F_FILE; +} + enum options { OPT_AUTORESTART, OPT_HELP, @@ -2336,9 +2462,9 @@ main(int argc, char **argv) }; struct record_device *d, *tmp; const char *output_arg = NULL; - bool multiple = false, all = false, with_libinput = false; + bool all = false, with_libinput = false; int ndevices; - int rc = 1; + int rc = EXIT_FAILURE; list_init(&ctx.devices); @@ -2354,12 +2480,13 @@ main(int argc, char **argv) case 'h': case OPT_HELP: usage(); - rc = 0; + rc = EXIT_SUCCESS; goto out; case OPT_AUTORESTART: if (!safe_atoi(optarg, &ctx.timeout) || ctx.timeout <= 0) { usage(); + rc = EXIT_INVALID_USAGE; goto out; } ctx.timeout = ctx.timeout * 1000; @@ -2371,8 +2498,7 @@ main(int argc, char **argv) case OPT_KEYCODES: ctx.show_keycodes = true; break; - case OPT_MULTIPLE: - multiple = true; + case OPT_MULTIPLE: /* deprecated */ break; case OPT_ALL: all = true; @@ -2380,51 +2506,96 @@ main(int argc, char **argv) case OPT_LIBINPUT: with_libinput = true; break; + default: + usage(); + rc = EXIT_INVALID_USAGE; + goto out; } } - if (all && multiple) { - fprintf(stderr, - "Only one of --multiple and --all allowed.\n"); - goto out; + ndevices = argc - optind; + + /* We allow for multiple arguments after the options, *one* of which + * may be the output file. That one must be the first or the last to + * prevent users from running + * libinput record /dev/input/event0 output.yml /dev/input/event1 + * because this will only backfire anyway. + */ + if (ndevices >= 1 && output_arg == NULL) { + char *first, *last; + enum ftype ftype_first; + + first = argv[optind]; + last = argv[argc - 1]; + + ftype_first = is_char_dev(first); + if (ndevices == 1) { + /* arg is *not* a char device, so let's assume it's + * the output file */ + if (ftype_first != F_DEVICE) { + output_arg = first; + optind++; + ndevices--; + } + /* multiple arguments, yay */ + } else { + enum ftype ftype_last = is_char_dev(last); + /* + first is device, last is file -> last + first is device, last is device -> noop + first is device, last !exist -> last + first is file, last is device -> first + first is file, last is file -> error + first is file, last !exist -> error + first !exist, last is device -> first + first !exist, last is file -> error + first !exit, last !exist -> error + */ +#define _m(f, l) (((f) << 8) | (l)) + switch (_m(ftype_first, ftype_last)) { + case _m(F_FILE, F_DEVICE): + case _m(F_FILE, F_NOEXIST): + case _m(F_NOEXIST, F_DEVICE): + output_arg = first; + optind++; + ndevices--; + break; + case _m(F_DEVICE, F_FILE): + case _m(F_DEVICE, F_NOEXIST): + output_arg = last; + ndevices--; + break; + case _m(F_DEVICE, F_DEVICE): + break; + case _m(F_FILE, F_FILE): + case _m(F_NOEXIST, F_FILE): + case _m(F_NOEXIST, F_NOEXIST): + fprintf(stderr, "Ambiguous device vs output file list. Please use --output-file.\n"); + rc = EXIT_INVALID_USAGE; + goto out; + } +#undef _m + } } + if (ctx.timeout > 0 && output_arg == NULL) { fprintf(stderr, "Option --autorestart requires --output-file\n"); + rc = EXIT_INVALID_USAGE; goto out; } ctx.outfile = safe_strdup(output_arg); - ndevices = argc - optind; - - if (multiple) { - if (output_arg == NULL) { - fprintf(stderr, - "Option --multiple requires --output-file\n"); - goto out; - } - - if (ndevices <= 1) { - fprintf(stderr, - "Option --multiple requires all device nodes on the commandline\n"); - goto out; - } - - for (int i = ndevices; i > 0; i -= 1) { - char *devnode = safe_strdup(argv[optind + i - 1]); - - if (!init_device(&ctx, devnode)) - goto out; - } - } else if (all) { + if (all) { char **devices; /* NULL-terminated */ char **d; if (output_arg == NULL) { fprintf(stderr, - "Option --all requires --output-file\n"); + "Option --all requires an output file\n"); + rc = EXIT_INVALID_USAGE; goto out; } @@ -2440,14 +2611,23 @@ main(int argc, char **argv) } strv_free(devices); - } else { - char *path; - - if (ndevices > 1) { - fprintf(stderr, "More than one device, do you want --multiple?\n"); + } else if (ndevices > 1) { + if (ndevices > 1 && output_arg == NULL) { + fprintf(stderr, + "Recording multiple devices requires an output file\n"); + rc = EXIT_INVALID_USAGE; goto out; } + for (int i = ndevices; i > 0; i -= 1) { + char *devnode = safe_strdup(argv[optind + i - 1]); + + if (!init_device(&ctx, devnode)) + goto out; + } + } else { + char *path; + path = ndevices <= 0 ? select_device() : safe_strdup(argv[optind++]); if (path == NULL) { goto out; diff --git a/tools/libinput-record.man b/tools/libinput-record.man index bed3d16..e85eb10 100644 --- a/tools/libinput-record.man +++ b/tools/libinput-record.man @@ -2,7 +2,7 @@ .SH NAME libinput\-record \- record kernel events .SH SYNOPSIS -.B libinput record [options] [\fI/dev/input/event0\fB] +.B libinput record [options] [\fI/dev/input/event0\fB [\fI/dev/input/event1\fB ...]] .SH DESCRIPTION .PP The \fBlibinput record\fR tool records kernel events from a device and @@ -10,14 +10,24 @@ prints them in a format that can later be replayed with the \fBlibinput replay(1)\fR tool. This tool needs to run as root to read from the device. .PP The output of this tool is YAML, see \fBFILE FORMAT\fR for more details. -By default it prints to stdout unless the \fB-o\fR option is given. +By default it prints to stdout unless an output file is provided. For +example, these are valid invocations: + +.B libinput record /dev/input/event3 touchpad.yml + +.B libinput record recording.yml + +.B libinput record --all all-devices.yml + +.B libinput record /dev/input/event3 /dev/input/event4 tp-and-keyboard.yml + .PP The events recorded are independent of libinput itself, updating or removing libinput will not change the event stream. .SH OPTIONS -If a device node is given, this tool opens that device node. Otherwise, -a list of devices is presented and the user can select the device to record. -If unsure, run without any arguments. +If one or more device nodes are given, this tool opens those device nodes. +Otherwise, a list of devices is presented and the user can select the device +to record. If unsure, run without any arguments. .TP 8 .B \-\-help Print help @@ -25,10 +35,9 @@ Print help .B \-\-all Record all \fI/dev/input/event*\fR devices available on the system. This option should be used in exceptional cases only, the output file is almost -always too noisy and replaying the recording may not be possible. Use -\fB\-\-multiple\fR instead. -This option requires that a \fB\-\-output-file\fR is specified and may not -be used together with \fB\-\-multiple\fR. +always too noisy and replaying the recording may not be possible. +This option requires \fB\-\-output-file\fR and no device +nodes may be provided on the commandline. .TP 8 .B \-\-autorestart=s Terminate the current recording after @@ -43,15 +52,11 @@ greater than 0. .TP 8 .B \-\-output-file=filename.yml .PD 1 -Specifies the output file to use. If \fB\-\-autorestart\fR or -\fB\-\-multiple\fR is given, the filename is used as prefix only. -.TP 8 -.B \-\-multiple -Record multiple devices at once, see section -.B RECORDING MULTIPLE DEVICES -This option requires that a -\fB\-\-output-file\fR is specified and that all devices to be recorded are -given on the commandline. +Specifies the output file to use. If \fB\-\-autorestart\fR is given, +the filename is used as prefix only. +Where \-\-output-file is not given and the first \fBor\fR last argument is +not an input device, the first \fBor\fR last argument will be the output +file. .TP 8 .B \-\-show\-keycodes Show keycodes as-is in the recording. By default, common keys are obfuscated @@ -67,16 +72,15 @@ for more details. .SH RECORDING MULTIPLE DEVICES Sometimes it is necessary to record the events from multiple devices simultaneously, e.g. when an interaction between a touchpad and a keyboard -causes a bug. The \fB\-\-multiple\fR option records multiple devices with +causes a bug. \fBlibinput record\fR records multiple devices with an identical time offset, allowing for correct replay of the interaction. .PP -The \fB\-\-multiple\fR option requires that an output filename is given. -This filename is used as prefix, with the event node number appended. +If multiple devices are recorded, an output filename must be provided. .PP All devices to be recorded must be provided on the commandline, an example invocation is: -.B libinput record \-\-multiple \-o tap-bug /dev/input/event3 /dev/input/event7 +.B libinput record \-o tap-bug /dev/input/event3 /dev/input/event7 Note that when recording multiple devices, only the first device is printed immediately, all other devices and their events are printed on exit. @@ -115,6 +119,7 @@ ndevices: 2 libinput: version: 1.10.0 system: + os: "fedora:26" kernel: "4.13.9-200.fc26.x86_64" dmi: "dmi:bvnLENOVO:bvrGJET72WW(2.22):bd02/21/2014:svnLENOVO:..." devices: @@ -215,6 +220,9 @@ libinput version .SS system Information about the system .TP 8 +.B os: string +Distribution ID and version, see \fIos-release(5)\fR +.TP 8 .B kernel: string Kernel version, see \fIuname(1)\fR .TP 8 diff --git a/tools/libinput-replay b/tools/libinput-replay index e574c6d..401dbbc 100755 --- a/tools/libinput-replay +++ b/tools/libinput-replay @@ -1,4 +1,4 @@ -#! /usr/libexec/platform-python +#!/usr/bin/env python3 # vim: set expandtab shiftwidth=4: # -*- Mode: python; coding: utf-8; indent-tabs-mode: nil -*- */ # @@ -28,6 +28,7 @@ import sys import time import multiprocessing import argparse +from pathlib import Path try: import libevdev @@ -110,8 +111,14 @@ def replay(device, verbose): return uinput = device['__uinput'] - offset = time.time() - handled_first_event = False + # The first event may have a nonzero offset but we want to replay + # immediately regardless. When replaying multiple devices, the first + # offset is the offset from the first event on any device. + offset = time.time() - device['__first_event_offset'] + + if offset < 0: + error('WARNING: event time offset is in the future, refusing to replay') + return # each 'evdev' set contains one SYN_REPORT so we only need to check for # the time offset once per event @@ -122,18 +129,11 @@ def replay(device, verbose): continue (sec, usec, evtype, evcode, value) = evdev[0] - - # The first event may have a nonzero offset but we want to replay - # immediately regardless. - if not handled_first_event: - offset -= sec + usec/1.e6 - handled_first_event = True - - evtime = sec + usec/1e6 + offset + evtime = sec + usec / 1e6 + offset now = time.time() - if evtime - now > 150/1e6: # 150 µs error margin - time.sleep(evtime - now - 150/1e6) + if evtime - now > 150 / 1e6: # 150 µs error margin + time.sleep(evtime - now - 150 / 1e6) evs = [libevdev.InputEvent(libevdev.evbit(e[2], e[3]), value=e[4], sec=e[0], usec=e[1]) for e in evdev] uinput.send_events(evs) @@ -141,6 +141,22 @@ def replay(device, verbose): print_events(uinput.devnode, device['__index'], evs) +def first_timestamp(device): + try: + events = fetch(device, 'events') + if events is None: + raise YamlException('No events from this device') + + evdev = fetch(events[0], 'evdev') + (sec, usec, *_) = evdev[0] + + return sec + usec / 1.e6 + + except YamlException: + import math + return math.inf + + def wrap(func, *args): try: func(*args) @@ -149,23 +165,20 @@ def wrap(func, *args): def loop(args, recording): - version = fetch(recording, 'version') - if version != SUPPORTED_FILE_VERSION: - raise YamlException('Invalid file format: {}, expected {}'.format(version, SUPPORTED_FILE_VERSION)) - - ndevices = fetch(recording, 'ndevices') devices = fetch(recording, 'devices') - if ndevices != len(devices): - error('WARNING: truncated file, expected {} devices, got {}'.format(ndevices, len(devices))) + + # All devices need to start replaying at the same time, so let's find + # the very first event and offset everything by that timestamp. + toffset = min([first_timestamp(d) for d in devices]) for idx, d in enumerate(devices): uinput = create(d) print('{}: {}'.format(uinput.devnode, uinput.name)) d['__uinput'] = uinput # cheaper to hide it in the dict then work around it d['__index'] = idx + d['__first_event_offset'] = toffset - stop = False - while not stop: + while True: input('Hit enter to start replaying') processes = [] @@ -182,18 +195,77 @@ def loop(args, recording): del processes +def create_device_quirk(device): + try: + quirks = fetch(device, 'quirks') + if not quirks: + return None + except YamlException: + return None + # Where the device has a quirk, we match on name, vendor and product. + # That's the best match we can assemble here from the info we have. + evdev = fetch(device, 'evdev') + name = fetch(evdev, 'name') + id = fetch(evdev, 'id') + quirk = ('[libinput-replay {name}]\n' + 'MatchName={name}\n' + 'MatchVendor=0x{id[1]:04X}\n' + 'MatchProduct=0x{id[2]:04X}\n').format(name=name, id=id) + quirk += '\n'.join(quirks) + return quirk + + +def setup_quirks(recording): + devices = fetch(recording, 'devices') + overrides = None + quirks = [] + for d in devices: + if 'quirks' in d: + quirk = create_device_quirk(d) + if quirk: + quirks.append(quirk) + if not quirks: + return None + + overrides = Path('/etc/libinput/local-overrides.quirks') + if overrides.exists(): + print('{} exists, please move it out of the way first'.format(overrides), file=sys.stderr) + sys.exit(1) + + overrides.parent.mkdir(exist_ok=True) + with overrides.open('w+') as fd: + fd.write('# This file was generated by libinput replay\n') + fd.write('# Unless libinput replay is running right now, remove this file.\n') + fd.write('\n\n'.join(quirks)) + + return overrides + + +def check_file(recording): + version = fetch(recording, 'version') + if version != SUPPORTED_FILE_VERSION: + raise YamlException('Invalid file format: {}, expected {}'.format(version, SUPPORTED_FILE_VERSION)) + + ndevices = fetch(recording, 'ndevices') + devices = fetch(recording, 'devices') + if ndevices != len(devices): + error('WARNING: truncated file, expected {} devices, got {}'.format(ndevices, len(devices))) + + def main(): - parser = argparse.ArgumentParser( - description='Replay a device recording' - ) + parser = argparse.ArgumentParser(description='Replay a device recording') parser.add_argument('recording', metavar='recorded-file.yaml', type=str, help='Path to device recording') parser.add_argument('--verbose', action='store_true') args = parser.parse_args() + quirks_file = None + try: with open(args.recording) as f: y = yaml.safe_load(f) + check_file(y) + quirks_file = setup_quirks(y) loop(args, y) except KeyboardInterrupt: pass @@ -201,6 +273,9 @@ def main(): error('Error: failed to open device: {}'.format(e)) except YamlException as e: error('Error: failed to parse recording: {}'.format(e)) + finally: + if quirks_file: + quirks_file.unlink() if __name__ == '__main__': diff --git a/tools/libinput-tool.c b/tools/libinput-tool.c index 7195de0..d619240 100644 --- a/tools/libinput-tool.c +++ b/tools/libinput-tool.c @@ -23,19 +23,13 @@ #include "config.h" -#include #include -#include #include -#include -#include -#include -#include -#include #include #include "shared.h" + static void usage(void) { @@ -58,6 +52,9 @@ usage(void) " measure \n" " Measure various device properties. See the man page for more info\n" "\n" + " analyze \n" + " Analyze device events. See the man page for more info\n" + "\n" " record\n" " Record event stream from a device node. See the man page for more info\n" "\n" diff --git a/tools/libinput.man b/tools/libinput.man index a2e678e..7c09dd0 100644 --- a/tools/libinput.man +++ b/tools/libinput.man @@ -39,26 +39,23 @@ Print all events as seen by libinput .B libinput\-debug\-gui(1) Show a GUI to visualize libinput's events .TP 8 +.B libinput\-debug\-tablet(1) +A commandline tool to debug tablet axis values +.TP 8 .B libinput\-list\-devices(1) List all devices recognized by libinput .TP 8 .B libinput\-measure(1) Measure various properties of devices .TP 8 -.B libinput\-measure\-touch\-size(1) -Measure touch size and orientation -.TP 8 -.B libinput\-measure\-touchpad\-tap(1) -Measure tap-to-click time -.TP 8 -.B libinput\-measure\-touchpad\-pressure(1) -Measure touch pressure -.TP 8 .B libinput\-record(1) Record the events from a device .TP 8 .B libinput\-replay(1) Replay the events from a device +.TP 8 +.B libinput\-analyze(1) +Analyze events from a device .SH LIBINPUT Part of the .B libinput(1) diff --git a/tools/shared.c b/tools/shared.c index 02c09dd..af79127 100644 --- a/tools/shared.c +++ b/tools/shared.c @@ -23,21 +23,25 @@ #include +#include #include #include #include #include +#include #include #include #include #include #include +#include #include -#include #include "builddir.h" #include "shared.h" +#include "util-macros.h" +#include "util-strings.h" LIBINPUT_ATTRIBUTE_PRINTF(3, 0) static void @@ -79,6 +83,7 @@ tools_init_options(struct tools_options *options) options->click_method = -1; options->scroll_method = -1; options->scroll_button = -1; + options->scroll_button_lock = -1; options->speed = 0.0; options->profile = LIBINPUT_CONFIG_ACCEL_PROFILE_NONE; } @@ -194,6 +199,12 @@ tools_parse_option(int option, return 1; } break; + case OPT_SCROLL_BUTTON_LOCK_ENABLE: + options->scroll_button_lock = true; + break; + case OPT_SCROLL_BUTTON_LOCK_DISABLE: + options->scroll_button_lock = false; + break; case OPT_SPEED: if (!optarg) return 1; @@ -294,14 +305,15 @@ out: } static struct libinput * -tools_open_device(const char *path, bool verbose, bool *grab) +tools_open_device(const char **paths, bool verbose, bool *grab) { struct libinput_device *device; struct libinput *li; + const char **p = paths; li = libinput_path_create_context(&interface, grab); if (!li) { - fprintf(stderr, "Failed to initialize context from %s\n", path); + fprintf(stderr, "Failed to initialize path context\n"); return NULL; } @@ -310,11 +322,15 @@ tools_open_device(const char *path, bool verbose, bool *grab) libinput_log_set_priority(li, LIBINPUT_LOG_PRIORITY_DEBUG); } - device = libinput_path_add_device(li, path); - if (!device) { - fprintf(stderr, "Failed to initialize device %s\n", path); - libinput_unref(li); - li = NULL; + while (*p) { + device = libinput_path_add_device(li, *p); + if (!device) { + fprintf(stderr, "Failed to initialize device %s\n", *p); + libinput_unref(li); + li = NULL; + break; + } + p++; } return li; @@ -332,7 +348,7 @@ tools_setenv_quirks_dir(void) struct libinput * tools_open_backend(enum tools_backend which, - const char *seat_or_device, + const char **seat_or_device, bool verbose, bool *grab) { @@ -342,7 +358,7 @@ tools_open_backend(enum tools_backend which, switch (which) { case BACKEND_UDEV: - li = tools_open_udev(seat_or_device, verbose, grab); + li = tools_open_udev(seat_or_device[0], verbose, grab); break; case BACKEND_DEVICE: li = tools_open_device(seat_or_device, verbose, grab); @@ -403,6 +419,10 @@ tools_device_apply_config(struct libinput_device *device, if (options->scroll_button != -1) libinput_device_config_scroll_set_button(device, options->scroll_button); + if (options->scroll_button_lock != -1) + libinput_device_config_scroll_set_button_lock(device, + options->scroll_button_lock); + if (libinput_device_config_accel_is_available(device)) { libinput_device_config_accel_set_speed(device, @@ -541,8 +561,7 @@ tools_exec_command(const char *prefix, int real_argc, char **real_argv) if (rc) { if (errno == ENOENT) { fprintf(stderr, - "libinput: %s is not a libinput command or not installed. " - "See 'libinput --help'\n", + "libinput: %s is not installed\n", command); return EXIT_INVALID_USAGE; } else { @@ -596,13 +615,15 @@ tools_list_device_quirks(struct quirks_context *ctx, if (!quirks) return; - q = QUIRK_MODEL_ALPS_TOUCHPAD; + q = QUIRK_MODEL_ALPS_SERIAL_TOUCHPAD; do { if (quirks_has_quirk(quirks, q)) { const char *name; + bool b; name = quirk_get_name(q); - snprintf(buf, sizeof(buf), "%s=1", name); + quirks_get_bool(quirks, q, &b); + snprintf(buf, sizeof(buf), "%s=%d", name, b ? 1 : 0); callback(userdata, buf); } } while(++q < _QUIRK_LAST_MODEL_QUIRK_); @@ -642,6 +663,7 @@ tools_list_device_quirks(struct quirks_context *ctx, break; case QUIRK_ATTR_LID_SWITCH_RELIABILITY: case QUIRK_ATTR_KEYBOARD_INTEGRATION: + case QUIRK_ATTR_TRACKPOINT_INTEGRATION: case QUIRK_ATTR_TPKBCOMBO_LAYOUT: case QUIRK_ATTR_MSC_TIMESTAMP: quirks_get_string(quirks, q, &s); diff --git a/tools/shared.h b/tools/shared.h index e2a6d66..730e1a4 100644 --- a/tools/shared.h +++ b/tools/shared.h @@ -51,6 +51,8 @@ enum configuration_options { OPT_CLICK_METHOD, OPT_SCROLL_METHOD, OPT_SCROLL_BUTTON, + OPT_SCROLL_BUTTON_LOCK_ENABLE, + OPT_SCROLL_BUTTON_LOCK_DISABLE, OPT_SPEED, OPT_PROFILE, OPT_DISABLE_SENDEVENTS, @@ -73,6 +75,8 @@ enum configuration_options { { "disable-middlebutton", no_argument, 0, OPT_MIDDLEBUTTON_DISABLE }, \ { "enable-dwt", no_argument, 0, OPT_DWT_ENABLE }, \ { "disable-dwt", no_argument, 0, OPT_DWT_DISABLE }, \ + { "enable-scroll-button-lock", no_argument, 0, OPT_SCROLL_BUTTON_LOCK_ENABLE }, \ + { "disable-scroll-button-lock",no_argument, 0, OPT_SCROLL_BUTTON_LOCK_DISABLE }, \ { "set-click-method", required_argument, 0, OPT_CLICK_METHOD }, \ { "set-scroll-method", required_argument, 0, OPT_SCROLL_METHOD }, \ { "set-scroll-button", required_argument, 0, OPT_SCROLL_BUTTON }, \ @@ -100,6 +104,7 @@ struct tools_options { enum libinput_config_scroll_method scroll_method; enum libinput_config_tap_button_map tap_map; int scroll_button; + int scroll_button_lock; double speed; int dwt; enum libinput_config_accel_profile profile; @@ -111,7 +116,7 @@ int tools_parse_option(int option, const char *optarg, struct tools_options *options); struct libinput* tools_open_backend(enum tools_backend which, - const char *seat_or_device, + const char **seat_or_devices, bool verbose, bool *grab); void tools_device_apply_config(struct libinput_device *device, diff --git a/tools/test_tool_option_parsing.py b/tools/test_tool_option_parsing.py new file mode 100755 index 0000000..373aa8b --- /dev/null +++ b/tools/test_tool_option_parsing.py @@ -0,0 +1,363 @@ +#!/usr/bin/env python3 +# vim: set expandtab shiftwidth=4: +# -*- Mode: python; coding: utf-8; indent-tabs-mode: nil -*- */ +# +# Copyright © 2018 Red Hat, Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice (including the next +# paragraph) shall be included in all copies or substantial portions of the +# Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. + +import os +import resource +import sys +import subprocess +import logging + +try: + import pytest +except ImportError: + print('Failed to import pytest. Skipping.', file=sys.stderr) + sys.exit(77) + + +logger = logging.getLogger('test') +logger.setLevel(logging.DEBUG) + +if '@DISABLE_WARNING@' != 'yes': + print('This is the source file, run the one in the meson builddir instead') + sys.exit(1) + + +def _disable_coredump(): + resource.setrlimit(resource.RLIMIT_CORE, (0, 0)) + + +def run_command(args): + logger.debug('run command: {}'.format(' '.join(args))) + with subprocess.Popen(args, preexec_fn=_disable_coredump, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) as p: + try: + p.wait(0.7) + except subprocess.TimeoutExpired: + p.send_signal(3) # SIGQUIT + stdout, stderr = p.communicate(timeout=5) + if p.returncode == -3: + p.returncode = 0 + return p.returncode, stdout.decode('UTF-8'), stderr.decode('UTF-8') + + +class LibinputTool(object): + libinput_tool = 'libinput' + subtool = None + + def __init__(self, subtool=None): + self.libinput_tool = "@TOOL_PATH@" + self.subtool = subtool + + def run_command(self, args): + args = [self.libinput_tool] + args + if self.subtool is not None: + args.insert(1, self.subtool) + + return run_command(args) + + def run_command_success(self, args): + rc, stdout, stderr = self.run_command(args) + # if we're running as user, we might fail the command but we should + # never get rc 2 (invalid usage) + assert rc in [0, 1], (stdout, stderr) + return stdout, stderr + + def run_command_invalid(self, args): + rc, stdout, stderr = self.run_command(args) + assert rc == 2, (rc, stdout, stderr) + return rc, stdout, stderr + + def run_command_unrecognized_option(self, args): + rc, stdout, stderr = self.run_command(args) + assert rc == 2, (rc, stdout, stderr) + assert stdout.startswith('Usage') or stdout == '' + assert 'unrecognized option' in stderr + + def run_command_missing_arg(self, args): + rc, stdout, stderr = self.run_command(args) + assert rc == 2, (rc, stdout, stderr) + assert stdout.startswith('Usage') or stdout == '' + assert 'requires an argument' in stderr + + def run_command_unrecognized_tool(self, args): + rc, stdout, stderr = self.run_command(args) + assert rc == 2, (rc, stdout, stderr) + assert stdout.startswith('Usage') or stdout == '' + assert 'is not installed' in stderr + + +class LibinputDebugGui(LibinputTool): + def __init__(self, subtool='debug-gui'): + assert subtool == 'debug-gui' + super().__init__(subtool) + + debug_gui_enabled = '@MESON_ENABLED_DEBUG_GUI@' == 'True' + if not debug_gui_enabled: + pytest.skip() + + if not os.getenv('DISPLAY') and not os.getenv('WAYLAND_DISPLAY'): + pytest.skip() + + # 77 means gtk_init() failed, which is probably because you can't + # connect to the display server. + rc, _, _ = self.run_command(['--help']) + if rc == 77: + pytest.skip() + + +def get_tool(subtool=None): + if subtool == 'debug-gui': + return LibinputDebugGui() + else: + return LibinputTool(subtool) + + +@pytest.fixture +def libinput(): + return get_tool() + + +@pytest.fixture(params=['debug-events', 'debug-gui']) +def libinput_debug_tool(request): + yield get_tool(request.param) + + +@pytest.fixture +def libinput_debug_events(): + return get_tool('debug-events') + + +@pytest.fixture +def libinput_debug_gui(): + return get_tool('debug-gui') + + +@pytest.fixture +def libinput_record(): + return get_tool('record') + + +def test_help(libinput): + stdout, stderr = libinput.run_command_success(['--help']) + assert stdout.startswith('Usage:') + assert stderr == '' + + +def test_version(libinput): + stdout, stderr = libinput.run_command_success(['--version']) + assert stdout.startswith('1') + assert stderr == '' + + +@pytest.mark.parametrize('argument', ['--banana', '--foo', '--quiet', '--verbose']) +def test_invalid_arguments(libinput, argument): + libinput.run_command_unrecognized_option([argument]) + + +@pytest.mark.parametrize('tool', [['foo'], ['debug'], ['foo', '--quiet']]) +def test_invalid_tool(libinput, tool): + libinput.run_command_unrecognized_tool(tool) + + +def test_udev_seat(libinput_debug_tool): + libinput_debug_tool.run_command_missing_arg(['--udev']) + libinput_debug_tool.run_command_success(['--udev', 'seat0']) + libinput_debug_tool.run_command_success(['--udev', 'seat1']) + + +@pytest.mark.skipif(os.environ.get('UDEV_NOT_AVAILABLE'), reason='udev required') +def test_device_arg(libinput_debug_tool): + libinput_debug_tool.run_command_missing_arg(['--device']) + libinput_debug_tool.run_command_success(['--device', '/dev/input/event0']) + libinput_debug_tool.run_command_success(['--device', '/dev/input/event1']) + libinput_debug_tool.run_command_success(['/dev/input/event0']) + + +options = { + 'pattern': ['sendevents'], + # enable/disable options + 'enable-disable': [ + 'tap', + 'drag', + 'drag-lock', + 'middlebutton', + 'natural-scrolling', + 'left-handed', + 'dwt' + ], + # options with distinct values + 'enums': { + 'set-click-method': ['none', 'clickfinger', 'buttonareas'], + 'set-scroll-method': ['none', 'twofinger', 'edge', 'button'], + 'set-profile': ['adaptive', 'flat'], + 'set-tap-map': ['lrm', 'lmr'], + }, + # options with a range + 'ranges': { + 'set-speed': (float, -1.0, +1.0), + } +} + + +# Options that allow for glob patterns +@pytest.mark.parametrize('option', options['pattern']) +def test_options_pattern(libinput_debug_tool, option): + libinput_debug_tool.run_command_success(['--disable-{}'.format(option), '*']) + libinput_debug_tool.run_command_success(['--disable-{}'.format(option), 'abc*']) + + +@pytest.mark.parametrize('option', options['enable-disable']) +def test_options_enable_disable(libinput_debug_tool, option): + libinput_debug_tool.run_command_success(['--enable-{}'.format(option)]) + libinput_debug_tool.run_command_success(['--disable-{}'.format(option)]) + + +@pytest.mark.parametrize('option', options['enums'].items()) +def test_options_enums(libinput_debug_tool, option): + name, values = option + for v in values: + libinput_debug_tool.run_command_success(['--{}'.format(name), v]) + libinput_debug_tool.run_command_success(['--{}={}'.format(name, v)]) + + +@pytest.mark.parametrize('option', options['ranges'].items()) +def test_options_ranges(libinput_debug_tool, option): + name, values = option + range_type, minimum, maximum = values + assert range_type == float + step = (maximum - minimum) / 10.0 + value = minimum + while value < maximum: + libinput_debug_tool.run_command_success(['--{}'.format(name), str(value)]) + libinput_debug_tool.run_command_success(['--{}={}'.format(name, value)]) + value += step + libinput_debug_tool.run_command_success(['--{}'.format(name), str(maximum)]) + libinput_debug_tool.run_command_success(['--{}={}'.format(name, maximum)]) + + +def test_apply_to(libinput_debug_tool): + libinput_debug_tool.run_command_missing_arg(['--apply-to']) + libinput_debug_tool.run_command_success(['--apply-to', '*foo*']) + libinput_debug_tool.run_command_success(['--apply-to', 'foobar']) + libinput_debug_tool.run_command_success(['--apply-to', 'any']) + + +@pytest.mark.parametrize('args', [['--verbose'], ['--quiet'], + ['--verbose', '--quiet'], + ['--quiet', '--verbose']]) +def test_debug_events_verbose_quiet(libinput_debug_events, args): + libinput_debug_events.run_command_success(args) + + +@pytest.mark.parametrize('arg', ['--banana', '--foo', '--version']) +def test_invalid_args(libinput_debug_tool, arg): + libinput_debug_tool.run_command_unrecognized_option([arg]) + + +def test_libinput_debug_events_multiple_devices(libinput_debug_events): + libinput_debug_events.run_command_success(['--device', '/dev/input/event0', '/dev/input/event1']) + # same event path multiple times? meh, your problem + libinput_debug_events.run_command_success(['--device', '/dev/input/event0', '/dev/input/event0']) + libinput_debug_events.run_command_success(['/dev/input/event0', '/dev/input/event1']) + + +def test_libinput_debug_events_too_many_devices(libinput_debug_events): + # Too many arguments just bails with the usage message + rc, stdout, stderr = libinput_debug_events.run_command(['/dev/input/event0'] * 61) + assert rc == 2, (stdout, stderr) + + +@pytest.mark.parametrize('arg', ['--quiet']) +def test_libinput_debug_gui_invalid_arg(libinput_debug_gui, arg): + libinput_debug_gui.run_command_unrecognized_option([arg]) + + +def test_libinput_debug_gui_verbose(libinput_debug_gui): + libinput_debug_gui.run_command_success(['--verbose']) + + +@pytest.mark.parametrize('arg', ['--help', '--show-keycodes', '--with-libinput']) +def test_libinput_record_args(libinput_record, arg): + libinput_record.run_command_success([arg]) + + +def test_libinput_record_multiple_arg(libinput_record): + # this arg is deprecated and a noop + libinput_record.run_command_success(['--multiple']) + + +@pytest.fixture +def recording(tmp_path): + return str((tmp_path / 'record.out').resolve()) + + +def test_libinput_record_all(libinput_record, recording): + libinput_record.run_command_success(['--all', '-o', recording]) + libinput_record.run_command_success(['--all', recording]) + + +def test_libinput_record_outfile(libinput_record, recording): + libinput_record.run_command_success(['-o', recording]) + libinput_record.run_command_success(['--output-file', recording]) + libinput_record.run_command_success(['--output-file={}'.format(recording)]) + + +def test_libinput_record_single(libinput_record, recording): + libinput_record.run_command_success(['/dev/input/event0']) + libinput_record.run_command_success(['-o', recording, '/dev/input/event0']) + libinput_record.run_command_success(['/dev/input/event0', recording]) + libinput_record.run_command_success([recording, '/dev/input/event0']) + + +def test_libinput_record_multiple(libinput_record, recording): + libinput_record.run_command_success(['-o', recording, '/dev/input/event0', '/dev/input/event1']) + libinput_record.run_command_success([recording, '/dev/input/event0', '/dev/input/event1']) + libinput_record.run_command_success(['/dev/input/event0', '/dev/input/event1', recording]) + + +def test_libinput_record_autorestart(libinput_record, recording): + libinput_record.run_command_invalid(['--autorestart']) + libinput_record.run_command_invalid(['--autorestart=2']) + libinput_record.run_command_success(['-o', recording, '--autorestart=2']) + + +def main(): + args = ['-m', 'pytest'] + try: + import xdist # noqa + args += ['-n', 'auto'] + except ImportError: + logger.info('python-xdist missing, this test will be slow') + pass + + args += ['@MESON_BUILD_ROOT@'] + + os.environ['LIBINPUT_RUNNING_TEST_SUITE'] = '1' + + return subprocess.run([sys.executable] + args).returncode + + +if __name__ == '__main__': + raise SystemExit(main()) diff --git a/udev/80-libinput-device-groups.rules.in b/udev/80-libinput-device-groups.rules.in index 1a26f78..1c40143 100644 --- a/udev/80-libinput-device-groups.rules.in +++ b/udev/80-libinput-device-groups.rules.in @@ -1,9 +1,6 @@ ACTION!="add|change", GOTO="libinput_device_group_end" KERNEL!="event[0-9]*", GOTO="libinput_device_group_end" -ATTRS{phys}=="?*", \ - ENV{LIBINPUT_DEVICE_GROUP}=="", \ - PROGRAM="@UDEV_TEST_PATH@libinput-device-group %S%p", \ - ENV{LIBINPUT_DEVICE_GROUP}="%c" +ATTRS{phys}=="?*", IMPORT{program}="@UDEV_TEST_PATH@libinput-device-group %S%p" LABEL="libinput_device_group_end" diff --git a/udev/90-libinput-fuzz-override.rules.in b/udev/90-libinput-fuzz-override.rules.in index e3d8e53..81f76cd 100644 --- a/udev/90-libinput-fuzz-override.rules.in +++ b/udev/90-libinput-fuzz-override.rules.in @@ -6,15 +6,22 @@ ACTION!="add|change", GOTO="libinput_fuzz_override_end" KERNEL!="event*", GOTO="libinput_fuzz_override_end" -# libinput-fuzz-override must only be called once per device, otherwise -# we'll lose the fuzz information +# Two-step process: fuzz-extract sets the LIBINPUT_FUZZ property and +# fuzz-to-zero sets the kernel fuzz to zero. They must be in IMPORT and RUN, +# respectively, to correctly interact with the 60-evdev.hwdb +# +# Drawback: if this rule is triggered more than once, we'll lose the fuzz +# information (because the kernel fuzz will then be zero). Nothing we can do +# about that. ATTRS{capabilities/abs}!="0", \ ENV{ID_INPUT_TOUCHPAD}=="1", \ - IMPORT{program}="@UDEV_TEST_PATH@libinput-fuzz-override %S%p", \ + IMPORT{program}="@UDEV_TEST_PATH@libinput-fuzz-extract %S%p", \ + RUN{program}+="@UDEV_TEST_PATH@libinput-fuzz-to-zero %S%p", \ GOTO="libinput_fuzz_override_end" ATTRS{capabilities/abs}!="0", \ ENV{ID_INPUT_TOUCHSCREEN}=="1", \ - IMPORT{program}="@UDEV_TEST_PATH@libinput-fuzz-override %S%p", \ + IMPORT{program}="@UDEV_TEST_PATH@libinput-fuzz-extract %S%p", \ + RUN{program}+="@UDEV_TEST_PATH@libinput-fuzz-to-zero %S%p", \ GOTO="libinput_fuzz_override_end" LABEL="libinput_fuzz_override_end" diff --git a/udev/libinput-device-group.c b/udev/libinput-device-group.c index dfcf9e0..65d22ec 100644 --- a/udev/libinput-device-group.c +++ b/udev/libinput-device-group.c @@ -95,7 +95,7 @@ static void wacom_handle_ekr(struct udev_device *device, int *vendor_id, int *product_id, - const char **phys_attr) + char **phys_attr) { struct udev *udev; struct udev_enumerate *e; @@ -138,7 +138,7 @@ wacom_handle_ekr(struct udev_device *device, *product_id = pid; best_dist = dist; - free((char*)*phys_attr); + free(*phys_attr); *phys_attr = strdup(phys); } } @@ -155,8 +155,7 @@ int main(int argc, char **argv) struct udev *udev = NULL; struct udev_device *device = NULL; const char *syspath, - *phys = NULL, - *physmatch = NULL; + *phys = NULL; const char *product; int bustype, vendor_id, product_id, version; char group[1024]; @@ -208,6 +207,8 @@ int main(int argc, char **argv) &version) != 4) { snprintf(group, sizeof(group), "%s:%s", product, phys); } else { + char *physmatch = NULL; + #if HAVE_LIBWACOM_GET_PAIRED_DEVICE if (vendor_id == VENDOR_ID_WACOM) { if (product_id == PRODUCT_ID_WACOM_EKR) @@ -228,6 +229,8 @@ int main(int argc, char **argv) vendor_id, product_id, physmatch ? physmatch : phys); + + free(physmatch); } str = strstr(group, "/input"); @@ -244,7 +247,7 @@ int main(int argc, char **argv) if (str && str > strrchr(group, '-')) *str = '\0'; - printf("%s\n", group); + printf("LIBINPUT_DEVICE_GROUP=%s\n", group); rc = 0; out: diff --git a/udev/libinput-fuzz-extract.c b/udev/libinput-fuzz-extract.c new file mode 100644 index 0000000..48ef79c --- /dev/null +++ b/udev/libinput-fuzz-extract.c @@ -0,0 +1,146 @@ +/* + * Copyright © 2015 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "util-prop-parsers.h" +#include "util-macros.h" + +/** + * For a non-zero fuzz on the x/y axes, print that fuzz as property and + * reset the kernel's fuzz to 0. + * https://bugs.freedesktop.org/show_bug.cgi?id=105202 + */ +static void +handle_absfuzz(struct udev_device *device) +{ + const char *devnode; + struct libevdev *evdev = NULL; + int fd = -1; + int rc; + unsigned int *code; + unsigned int axes[] = {ABS_X, + ABS_Y, + ABS_MT_POSITION_X, + ABS_MT_POSITION_Y}; + + devnode = udev_device_get_devnode(device); + if (!devnode) + goto out; + + fd = open(devnode, O_RDONLY); + if (fd < 0) + goto out; + + rc = libevdev_new_from_fd(fd, &evdev); + if (rc != 0) + goto out; + + if (!libevdev_has_event_type(evdev, EV_ABS)) + goto out; + + ARRAY_FOR_EACH(axes, code) { + int fuzz; + + fuzz = libevdev_get_abs_fuzz(evdev, *code); + if (fuzz) + printf("LIBINPUT_FUZZ_%02x=%d\n", *code, fuzz); + } + +out: + close(fd); + libevdev_free(evdev); +} + +/** + * Where a device has EVDEV_ABS_... set with a fuzz, that fuzz hasn't been + * applied to the kernel yet. So we need to extract it ourselves **and** + * update the property so the kernel won't actually set it later. + */ +static void +handle_evdev_abs(struct udev_device *device) +{ + unsigned int *code; + unsigned int axes[] = {ABS_X, + ABS_Y, + ABS_MT_POSITION_X, + ABS_MT_POSITION_Y}; + + ARRAY_FOR_EACH(axes, code) { + const char *prop; + char name[64]; + uint32_t mask; + struct input_absinfo abs; + + snprintf(name, sizeof(name), "EVDEV_ABS_%02X", *code); + prop = udev_device_get_property_value(device, name); + if (!prop) + continue; + + mask = parse_evdev_abs_prop(prop, &abs); + if (mask & ABS_MASK_FUZZ) + printf("LIBINPUT_FUZZ_%02x=%d\n", *code, abs.fuzz); + } +} + +int main(int argc, char **argv) +{ + int rc = 1; + struct udev *udev = NULL; + struct udev_device *device = NULL; + const char *syspath; + + if (argc != 2) + return 1; + + syspath = argv[1]; + + udev = udev_new(); + if (!udev) + goto out; + + device = udev_device_new_from_syspath(udev, syspath); + if (!device) + goto out; + + handle_absfuzz(device); + handle_evdev_abs(device); + + rc = 0; + +out: + if (device) + udev_device_unref(device); + if (udev) + udev_unref(udev); + + return rc; +} diff --git a/udev/libinput-fuzz-to-zero.c b/udev/libinput-fuzz-to-zero.c new file mode 100644 index 0000000..a8767be --- /dev/null +++ b/udev/libinput-fuzz-to-zero.c @@ -0,0 +1,112 @@ +/* + * Copyright © 2015 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include "util-macros.h" + +static void +reset_absfuzz_to_zero(struct udev_device *device) +{ + const char *devnode; + struct libevdev *evdev = NULL; + int fd = -1; + int rc; + unsigned int *code; + unsigned int axes[] = {ABS_X, + ABS_Y, + ABS_MT_POSITION_X, + ABS_MT_POSITION_Y}; + + devnode = udev_device_get_devnode(device); + if (!devnode) + goto out; + + fd = open(devnode, O_RDWR); + if (fd < 0) + goto out; + + rc = libevdev_new_from_fd(fd, &evdev); + if (rc != 0) + goto out; + + if (!libevdev_has_event_type(evdev, EV_ABS)) + goto out; + + ARRAY_FOR_EACH(axes, code) { + struct input_absinfo abs; + int fuzz; + + fuzz = libevdev_get_abs_fuzz(evdev, *code); + if (!fuzz) + continue; + + abs = *libevdev_get_abs_info(evdev, *code); + abs.fuzz = 0; + libevdev_kernel_set_abs_info(evdev, *code, &abs); + } + +out: + close(fd); + libevdev_free(evdev); +} + +int main(int argc, char **argv) +{ + int rc = 1; + struct udev *udev = NULL; + struct udev_device *device = NULL; + const char *syspath; + + if (argc != 2) + return 1; + + syspath = argv[1]; + + udev = udev_new(); + if (!udev) + goto out; + + device = udev_device_new_from_syspath(udev, syspath); + if (!device) + goto out; + + reset_absfuzz_to_zero(device); + + rc = 0; + +out: + if (device) + udev_device_unref(device); + if (udev) + udev_unref(udev); + + return rc; +} diff --git a/udev/test-libinput-fuzz-extract.c b/udev/test-libinput-fuzz-extract.c new file mode 100644 index 0000000..05d9f39 --- /dev/null +++ b/udev/test-libinput-fuzz-extract.c @@ -0,0 +1,126 @@ +/* + * Copyright © 2019 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include + +/* remove the main() from the included program so we can define our own */ +#define main __disabled +int main(int argc, char **argv); +#include "libinput-fuzz-extract.c" +#undef main + +START_TEST(test_parse_ev_abs) +{ + struct test { + uint32_t which; + const char *prop; + int min, max, res, fuzz, flat; + + } tests[] = { + { .which = (MIN|MAX), + .prop = "1:2", + .min = 1, .max = 2 }, + { .which = (MIN|MAX), + .prop = "1:2:", + .min = 1, .max = 2 }, + { .which = (MIN|MAX|RES), + .prop = "10:20:30", + .min = 10, .max = 20, .res = 30 }, + { .which = (RES), + .prop = "::100", + .res = 100 }, + { .which = (MIN), + .prop = "10:", + .min = 10 }, + { .which = (MAX|RES), + .prop = ":10:1001", + .max = 10, .res = 1001 }, + { .which = (MIN|MAX|RES|FUZZ), + .prop = "1:2:3:4", + .min = 1, .max = 2, .res = 3, .fuzz = 4}, + { .which = (MIN|MAX|RES|FUZZ|FLAT), + .prop = "1:2:3:4:5", + .min = 1, .max = 2, .res = 3, .fuzz = 4, .flat = 5}, + { .which = (MIN|RES|FUZZ|FLAT), + .prop = "1::3:4:50", + .min = 1, .res = 3, .fuzz = 4, .flat = 50}, + { .which = FUZZ|FLAT, + .prop = ":::5:60", + .fuzz = 5, .flat = 60}, + { .which = FUZZ, + .prop = ":::5:", + .fuzz = 5 }, + { .which = RES, .prop = "::12::", + .res = 12 }, + /* Malformed property but parsing this one makes us more + * future proof */ + { .which = (RES|FUZZ|FLAT), .prop = "::12:1:2:3:4:5:6", + .res = 12, .fuzz = 1, .flat = 2 }, + { .which = 0, .prop = ":::::" }, + { .which = 0, .prop = ":" }, + { .which = 0, .prop = "" }, + { .which = 0, .prop = ":asb::::" }, + { .which = 0, .prop = "foo" }, + }; + struct test *t; + + ARRAY_FOR_EACH(tests, t) { + struct input_absinfo abs; + uint32_t mask; + + mask = parse_ev_abs_prop(t->prop, &abs); + ck_assert_int_eq(mask, t->which); + + if (t->which & MIN) + ck_assert_int_eq(abs.minimum, t->min); + if (t->which & MAX) + ck_assert_int_eq(abs.maximum, t->max); + if (t->which & RES) + ck_assert_int_eq(abs.resolution, t->res); + if (t->which & FUZZ) + ck_assert_int_eq(abs.fuzz, t->fuzz); + if (t->which & FLAT) + ck_assert_int_eq(abs.flat, t->flat); + } +} +END_TEST + +int main(int argc, char **argv) { + + SRunner *sr = srunner_create(NULL); + Suite *s = suite_create("fuzz-override"); + TCase *tc = tcase_create("parser"); + int nfailed; + + tcase_add_test(tc, test_parse_ev_abs); + suite_add_tcase(s, tc); + srunner_add_suite(sr, s); + + srunner_run_all(sr, CK_NORMAL); + nfailed = srunner_ntests_failed(sr); + srunner_free(sr); + + return nfailed; +}