Trackpad gestures on Linux desktop

Houses in San Fransisco by Bernard.
Houses in San Fransisco by Bernard.

SUMMARY On my Linux laptop, I like to have gestures support for my track pad to quickly switch to different screens (4 finger swipe) or go back/forward in the browser (3 finger swipe). I used the excellent gebaar-libinput for it, but it is unmaintained, and it fails to compile on newer versions of Linux. So I have made a simpler version, that is easy to compile and modify, and is called gestures.

2024-01-03 gestures wayland libinput

This new gestures package works for both Wayland and X11. To start, grab the sources and compile or use the Bit Powder repo to fetch the binary version for recent Debian/Raspbian versions. It uses libinput to track the input and executes a shell script based on the gestures. It executes the script $HOME/.config/gestures/three-to-left (with either three or four, and either left/`right`/`top`/`bottom`). This avoids a config script, and parsing it.

The original gebaar-libinput can be found at GitHub.

To go back/forward with three fingers, use the following scripts, listed below.

$HOME/.config/gestures/three-to-right

Swipe to right to go back in browsers.

#!/bin/sh
# Space thing is needed for Chromium based browsers in wayland
if [ -n "$WAYLAND_DISPLAY" ]; then
  wtype -P space -s 1 -p space -s 1 -M alt -P left -s 10 -m alt -p left
elif [ -n "$DISPLAY" ]; then
  xdotool key Alt+Left
fi

$HOME/.config/gestures/three-to-left

Swipe to left to go forward in browsers.

#!/bin/sh
# Space thing is needed for Chromium based browsers in wayland
if [ -n "$WAYLAND_DISPLAY" ]; then
  wtype -P space -s 1 -p space -s 1 -M alt -P right -s 10 -m alt -p right
elif [ -n "$DISPLAY" ]; then
  xdotool key Alt+Right
fi

$HOME/.config/gestures/four-to-left

Swipe to right to go in i3/sway to the previous workspace.

#!/bin/sh
i3-msg workspace next

$HOME/.config/gestures/four-to-right

Swipe to right to go in i3/sway to the previous workspace.

#!/bin/sh
i3-msg workspace prev

The complete C++ version

#include <poll.h>
#include <libinput.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>

#include <string>

static int libinput_open(const char* path, int flags, void*) {
  int fd = open(path, flags);
  return fd<0 ? -errno : fd;
}

static void libinput_close(int fd, void*) {
  close(fd);
}

static struct libinput_interface libinput_interface = {
        .open_restricted = libinput_open,
        .close_restricted = libinput_close,
};

int fingers = 0;
double x = 0.0;
double y = 0.0;

void handle_swipe_event_without_coords(libinput_event_gesture* gev) {
  int swipe_type = 5; // middle = no swipe
                     // 1 = left_up, 2 = up, 3 = right_up...
                     // 1 2 3
                     // 4 5 6
                     // 7 8 9
  const double OBLIQUE_RATIO = 0.414; // =~ tan(22.5);


  fingers = 0;
  x = y = 0;
}

int main(int argc, char** argv) {
  auto udev = udev_new();
  auto libinput = libinput_udev_create_context(&libinput_interface, nullptr, udev);
  if (libinput_udev_assign_seat(libinput, "seat0") != 0)
    return -1;

  // event loop
  struct pollfd fds{};
  fds.fd = libinput_get_fd(libinput);
  fds.events = POLLIN;
  fds.revents = 0;

  while (poll(&fds, 1, -1)>-1) {
    libinput_dispatch(libinput);
    while (auto libinput_event = libinput_get_event(libinput)) {
      switch (libinput_event_get_type(libinput_event)) {
        case LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN: {
          auto ge = libinput_event_get_gesture_event(libinput_event);
          fingers = libinput_event_gesture_get_finger_count(ge);
          x = y = 0;
          break;
        }
        case LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE: {
          auto ge = libinput_event_get_gesture_event(libinput_event);
          x += libinput_event_gesture_get_dx(ge);
          y += libinput_event_gesture_get_dy(ge);
          break;
        }
        case LIBINPUT_EVENT_GESTURE_SWIPE_END: {
          auto ge = libinput_event_get_gesture_event(libinput_event);
          std::string type;
          if (abs(x) > abs(y)) {
            type = x < 0 ? "left" : "right";
          } else {
            type = y < 0 ? "top" : "bottom";
          }
          fprintf(stderr, "gesture %s with %i fingers\n", type.c_str(), fingers);
          if (fingers == 3) {
            std::system((std::string(".config/gestures/three-to-") + type).c_str());
          } else if (fingers == 4) {
            std::system((std::string(".config/gestures/four-to-") + type).c_str());
          }
          break;
        }
        default:
          break;
      }

      libinput_event_destroy(libinput_event);
      libinput_dispatch(libinput);
    }
  }
  return 0;
}