Multi-touch gestures with native touchpad drivers on Linux

Announcing syngestures, the userland multi-touch daemon

Perhaps one of the biggest difficulties in setting up a Linux system for desktop/home use is the fragmentation of the ecosystem, with many different options claiming to get you from point a to somewhere in the vicinity of point b, each with their subtle differences (and at least a few “gotchas” along the way). An easy example: in 2020, you’d think there would be an easy answer to getting a trackpad/touchpad up and running with support for multi-touch gestures at least on par with the experience on Windows and macOS – after all, it’s been 12 years since Apple made multi-touch popular with 2008 MacBook Air.

The state of Linux multi-touch input in 2020

Unfortunately, it’s not so simple. First there was xf86-input-synaptics, initially written to specifically target Synaptics touchpads but later updated with support for many different devices – for the longest time this was both the best and the only way of getting Linux to recognize and support the most common trackpads. Along the way to no longer being a Synaptics-only PS/2 touchpad driver, xf86-input-synaptics started to use the evdev Linux kernel input driver, which also gave birth to the more generic input/mouse/touchpad xf86-input-evdev driver that used the same kernel abstraction, which most distributions now use because it featured some (limited) multi-touch gesture support and was viewed as the way forward. Then came Wayland with its “let’s rewrite everything” approach and brought with it libinput, as yet another replacement abstraction for all input devices. Unlike previous projects, libinput and its accompanying xf86-input-libinput X11 integration, promised native support for touchpads and multi-touch gestures, touting its superior abstraction as the reason why it could provide a better multi-touch experience where its predecessors couldn’t.

While it’s true that libinput comes with out-of-the-box gesture support, it’s unfortunately a gigantic step back in terms of the actual touchpad experience. As many people that switched from xf86-input-synaptics to xf86-input-libinput can attest (or anyone that’s tried libinput on a laptop after using Windows or macOS): it’s horrible. Regardless of how you tweak it (except perhaps if you have the exact make and model of touchpad the developers used), it feels like you’re either trying to push the cursor through mud or else chasing Blinky, Pinky, Inky and Clyde on the final level of PacMan and just can’t keep up. Instead of using the decades of work that went into the existing drivers, libinput just throws out the baby with the bathwater in the name of rewriting the stack and reinventing the wheel: the acceleration curves are extremely poor, the heuristics for determining acceleration give extremely unnatural results, and the entire experience makes one want to never try Linux on a laptop ever again.

Sidebar: if you haven’t already read it, this historical gem features Ted Selker explaining the decade of work that went into developing IBM’s Trackpoint, and the painstaking effort that it takes to create an input device (and accompanying driver/software) that is ergonomic, natural, intuitive, and user friendly. Anyone thinking of rewriting something as complex and subtle as a touchpad driver should read this before thinking they can do a better job and trying to foist their replacement on others.

Rather than risk falling into the same Wayland/libinput mistake in an attempt at creating “a newer, better alternative” (see XKCD #927) and especially given the fact that xf86-input-synaptics already does a really good job at providing a great – albeit single-touch – touchpad experience out-of-the-box, I wondered how hard it would be to simply add multi-touch gesture support on top of the synaptics input driver. My aspirations were not high: simply having two-finger forward and backward swipe navigation in Firefox would have been enough for me.

I was prepared to have to dig deep into the drivers to add support for multi-touch gestures, but it turned out not to be not very hard at all: as xf86-input-synaptics now uses libevdev under the hood, the driver-level support for reading multi-touch inputs from the trackpad itself is already fully implemented, just ignored. From there, it didn’t take long to create a proof-of-concept that would allow the xf86-input-synaptics driver to handle cursor motion, acceleration, deceleration, etc. but allow a userland program/daemon to monitor for and act upon multi-touch inputs (via the Linux multi-touch protocol).

Introducing Syngestures

syngesture is a configurable, multi-device capable, and rust-powered userland daemon that brings basic multi-touch support for one-, two-, and three-finger gestures to the venerable xf86-input-synaptics driver. It is, of course, fully open-source and released under the MIT license.

Like many other solutions, it was originally developed to scratch a personal itch, but after hearing from others with similar complaints, I decided to add some fit and polish (such as machine-wide and per-user config files and support for multiple devices with per-device configuration) that will hopefully make it more useful to the open source community at large. It was originally designed as an alternative to libinput and specifically intended for use with xf86-input-synaptics as explained above, but it should also work with xf86-input-evdev and other devices implementing the Linux Multi-Touch Protocol.

syngesture is available on GitHub and is considered ready for general use (modulo distribution-specific packages and system integrations for alternatives for manual configuration – e.g. initial device discovery – and deployment).

Download syngestures

5 thoughts on “Multi-touch gestures with native touchpad drivers on Linux

  1. Great post! It is awesome to see someone working on these things. I will definitely give this a go at some point!
    Do you think that a three finger drag gesture would be possible to set up with this system?
    Transitioning from MacOS to Linux was a breeze for the most part and I have no regrets whatsoever. The only thing I really miss are good touchpad gestures.

  2. There are several questions about this article that I’d like to ask.

    I read the github repo and you recommend using X11. Meanwhile, now more than ever it’s starting to show that many distros are transitioning into Wayland, especially Ubuntu with its big userbase. What do you think about the adoption, since as you mentioned, Wayland releases libinput?

    As far as I know, there are some promising implementation regarding touchpad gestures (I’m not talking about the rest — mouse acceleration, etc.), mainly Touchegg and ElementaryOS implementation (upcoming in eOS 6), and Gnome + Wayland implementation (upcoming in Gnome 40); the former is using X.Org. What is it that syngestures offers that differs among those two?

  3. Not sure if this is fully in scope for this project, but I have to complain about it somewhere: in addition to all the other problems with libinput (acceleration, clunkiness), they also simply do not support right- or middle-click and drag, and have stated on various mailing lists that it doesn’t seem like a worthwhile thing to add to their input state machine. This is despite being necessary to use a lot of desktop apps with a touchpad, and despite being present in synaptics, as well as windows and mac touchpad drivers. I’m excited to try out your Syngestures with synaptics to get back the (seemingly basic) ability to hold down right or middle mouse buttons, which I was forced to give up for the last few years to get reasonable multi-touch on my laptop.

  4. Hi I am having problem knowing the device. Can you kindly take a look and tell me what should I put in device. bcm 5974 is the trackpad’s name as per xinput list.

    `$ dmesg
    [ 3.910432] usb 2-1.8.3: New USB device found, idVendor=05ac, idProduct=0252, bcdDevice= 2.19
    [ 3.910437] usb 2-1.8.3: New USB device strings: Mfr=1, Product=2, SerialNumber=0
    [ 3.910440] usb 2-1.8.3: Product: Apple Internal Keyboard / Trackpad
    [ 3.910441] usb 2-1.8.3: Manufacturer: Apple Inc.
    [ 3.920923] input: Apple Inc. Apple Internal Keyboard / Trackpad as /devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.8/2-1.8.3/2-1.8.3:1.0/0003:05AC:0252.0002/input/input6
    [ 3.923149] raid6: sse2x1 gen() 8326 MB/s
    [ 3.923154] raid6: using algorithm sse2x2 gen() 10565 MB/s
    [ 3.940145] raid6: …. xor() 5950 MB/s, rmw enabled
    [ 3.940148] raid6: using ssse3x2 recovery algorithm
    [ 3.940560] xor: automatically using best checksumming function avx
    [ 3.940843] async_tx: api initialized (async)
    [ 3.968218] usb 2-1.8.1.1: new full-speed USB device number 7 using ehci-pci
    [ 3.973417] apple 0003:05AC:0252.0002: input,hidraw1: USB HID v1.11 Keyboard [Apple Inc. Apple Internal Keyboard / Trackpad] on usb-0000:00:1d.0-1.8.3/input0
    [ 3.973577] apple 0003:05AC:0252.0003: hidraw2: USB HID v1.11 Device [Apple Inc. Apple Internal Keyboard / Trackpad] on usb-0000:00:1d.0-1.8.3/input1
    [ 4.049132] usb 2-1.8.1.1: New USB device found, idVendor=05ac, idProduct=820a, bcdDevice= 1.00
    [ 4.049170] usb 2-1.8.1.1: New USB device strings: Mfr=0, Product=0, SerialNumber=0

    [ 7.403920] usbcore: registered new interface driver uvcvideo
    [ 7.798365] input: bcm5974 as /devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.8/2-1.8.3/2-1.8.3:1.2/input/input15
    [ 7.799077] usbcore: registered new interface driver bcm5974
    [ 8.410568] alg: No test for fips(ansi_cprng) (fips_ansi_cprng)

    $ lsinput
    /dev/input/event0
    bustype : BUS_HOST
    vendor : 0x0
    product : 0x5
    version : 0
    name : “Lid Switch”
    phys : “PNP0C0D/button/input0”
    bits ev : (null) (null)

    /dev/input/event1
    bustype : BUS_HOST
    vendor : 0x0
    product : 0x1
    version : 0
    name : “Power Button”
    phys : “PNP0C0C/button/input0”
    bits ev : (null) (null)

    /dev/input/event2
    bustype : BUS_HOST
    vendor : 0x0
    product : 0x3
    version : 0
    name : “Sleep Button”
    phys : “PNP0C0E/button/input0”
    bits ev : (null) (null)

    /dev/input/event3
    bustype : BUS_HOST
    vendor : 0x0
    product : 0x1
    version : 0
    name : “Power Button”
    phys : “LNXPWRBN/button/input0”
    bits ev : (null) (null)

    /dev/input/event4
    bustype : BUS_HOST
    vendor : 0x0
    product : 0x6
    version : 0
    name : “Video Bus”
    phys : “LNXVIDEO/video/input0”
    bits ev : (null) (null)

    /dev/input/event5
    bustype : BUS_USB
    vendor : 0x5ac
    product : 0x8242
    version : 273
    name : “Apple Computer, Inc. IR Receiver”
    phys : “usb-0000:00:1d.0-1.8.2/input0”
    uniq : “”
    bits ev : (null) (null) (null)

    /dev/input/event6
    bustype : BUS_USB
    vendor : 0x5ac
    product : 0x252
    version : 273
    name : “Apple Inc. Apple Internal Keyboa”
    phys : “usb-0000:00:1d.0-1.8.3/input0”
    uniq : “”
    bits ev : (null) (null) (null) (null) (null)

    /dev/input/event7
    bustype : BUS_USB
    vendor : 0x0
    product : 0x0
    version : 4
    name : “ACPI Virtual Keyboard Device”
    bits ev : (null) (null)

    /dev/input/event9
    bustype : (null)
    vendor : 0x0
    product : 0x0
    version : 0
    name : “HDA Intel PCH Headphone”
    phys : “ALSA”
    bits ev : (null) (null)

    /dev/input/event10
    bustype : (null)
    vendor : 0x0
    product : 0x0
    version : 0
    name : “HDA Intel PCH HDMI/DP,pcm=3”
    phys : “ALSA”
    bits ev : (null) (null)

    /dev/input/event11
    bustype : (null)
    vendor : 0x0
    product : 0x0
    version : 0
    name : “HDA Intel PCH HDMI/DP,pcm=7”
    phys : “ALSA”
    bits ev : (null) (null)

    /dev/input/event12
    bustype : (null)
    vendor : 0x0
    product : 0x0
    version : 0
    name : “HDA Intel PCH HDMI/DP,pcm=8”
    phys : “ALSA”
    bits ev : (null) (null)

    /dev/input/event13
    bustype : BUS_HOST
    vendor : 0x0
    product : 0x0
    version : 0
    name : “applesmc”
    bits ev : (null) (null)

    /dev/input/event14
    bustype : BUS_USB
    vendor : 0x5ac
    product : 0x8509
    version : 1302
    name : “FaceTime HD Camera (Built-in): ”
    phys : “usb-0000:00:1a.0-1.1/button”
    bits ev : (null) (null)

    /dev/input/event15
    bustype : BUS_USB
    vendor : 0x5ac
    product : 0x252
    version : 1
    name : “bcm5974”
    phys : “usb-0000:00:1d.0-1.8.3/input0”
    bits ev : (null) (null) (null)

    $ xinput list
    ⎡ Virtual core pointer id=2 [master pointer (3)]
    ⎜ ↳ Virtual core XTEST pointer id=4 [slave pointer (2)]
    ⎜ ↳ bcm5974 id=13 [slave pointer (2)]
    ⎣ Virtual core keyboard id=3 [master keyboard (2)]
    ↳ Virtual core XTEST keyboard id=5 [slave keyboard (3)]
    ↳ Power Button id=6 [slave keyboard (3)]
    ↳ Video Bus id=7 [slave keyboard (3)]
    ↳ Power Button id=8 [slave keyboard (3)]
    ↳ Sleep Button id=9 [slave keyboard (3)]
    ↳ FaceTime HD Camera (Built-in): id=10 [slave keyboard (3)]
    ↳ Apple Computer, Inc. IR Receiver id=11 [slave keyboard (3)]
    ↳ Apple Inc. Apple Internal Keyboard / Trackpad id=12 [slave keyboard (3)]
    ↳ ACPI Virtual Keyboard Device id=14 [slave keyboard (3)]

    `

Leave a Reply

Your email address will not be published. Required fields are marked *