Natural Swipe Workspace Switcher

UPDATE: The code shown here is not up to date anymore. See Touchgreat for an up to date post.

Ok, the title is not really good but I have no idea how to call it. I want to switch workspace with a 4-finger swipe, but not the simple way. All programms that feature this functionality simple execute a key combination that then switches the workspace. I want to really move the workspace with the swipe, like on a mac. look

Configuring Compiz

I don’t know if it is possible to do it without compiz because you need to be able to set very specific settings.
With the “Rotate Cube” plugin it is possible to initiate a cube rotion with a key combination and then rotate the cube with the mouse. This is the thing that is important for this to work.
Compiz settings

libinput

The best way on a modern linux system to recognize gestures is libinput. But for example in my ubuntu repository is version 1.2.x. This version is very old and does not work really good. I compiled version 1.5.2 from source, there are many tutorials out there for changing from synaptics to libinput.

libinput-debug-events

With libinput-debug-events it is possible to print all events that libinput registers. When swiping with multiple fingers the output looks like this:

1
2
3
4
5
6
7
event5 GESTURE_SWIPE_BEGIN +1.31s 4
event5 GESTURE_SWIPE_UPDATE +1.31s 4 -17.31/-4.64 (-48.99/-13.12 unaccelerated)
event5 GESTURE_SWIPE_UPDATE +1.33s 4 -26.35/-7.37 (-48.99/-13.69 unaccelerated)
event5 GESTURE_SWIPE_UPDATE +1.35s 4 -25.54/-8.73 (-36.75/-12.55 unaccelerated)
event5 GESTURE_SWIPE_UPDATE +1.37s 4 -23.92/-14.12 (-36.75/-21.68 unaccelerated)
event5 GESTURE_SWIPE_UPDATE +1.39s 4 -12.29/-8.02 (-22.75/-14.84 unaccelerated)
event5 GESTURE_SWIPE_END +1.43s 4

There is a GESTURE_SWIPE_BEGIN event, then multiple GESTURE_SWIPE_UPDATE and at the end a GESTURE_SWIPE_END.

So it should be possible to catch the BEGIN initiate a cube rotation with xdotool, rotate the cube and on an END stop the cube rotation.
The only problem is that mouse doesn’t move when swiping, so this also must be done with xdotool


The user must be in the group input
1
sudo gpasswd -a $USER input


Log off and back in afterwards.

xdotool

With the xdotool it is possible to simulate key events, mouse events and many other things.

The following script rotates the cube:

1
2
3
4
5
6
7
8
9
10
#!/bin/bash
xdotool keydown Control_L+Alt_L mousedown 1; # initiate the cube rotation
sleep 0.2; xdotool mousemove_relative -- -100 0; # move the mouse -100px on the x
sleep 0.2; xdotool mousemove_relative -- -100 0;
sleep 0.2; xdotool mousemove_relative -- -100 0;
sleep 0.2; xdotool mousemove_relative -- -100 0;
sleep 0.2; xdotool mousemove_relative -- -100 0;
sleep 0.2; xdotool mousemove_relative -- -100 0;
sleep 0.2; xdotool mousemove_relative -- -100 0;
xdotool mouseup 1 keyup Control_L+Alt_L; # stop the cube rotation

The plan

So this is my plan:

  1. Grab the GESTURE_PINCH_BEGIN event from libinput-debug-events and start the cube rotation with xdotool
  2. On GESTURE_PINCH_UPDATE move the mouse with xdotool
  3. Stop the switching on GESTURE_PINCH_END

The code

I will just drop the code here and describe it with comments:

The code []view raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
#!/usr/bin/python3
import subprocess
#CONFIG
# The number of the fingers, 3 or 4
FINGER_COUNT = 4
# Factor of the mouse speed. Higher makes the cube rotating faster
SPEED = 1.5
# Only rotate the cube on the x-axis
ONLY_X = False
# Key combination to initiate the cube rotation
KEYCOMBO = 'Control_L+Alt_L'
# The path to the touchpad input device
INPUT_DEVICE = '/dev/input/event5'
# This function gets the finger count from the libinput-debug-events output line
# The line is not formatted really beautiful so it is quite a pain to get the information needed out of it.
def getFingerCount(line):
fingerCountDirection = line.split('\t')[2]
fingerCountStr = ''
if fingerCountDirection.find(' ') == -1:
fingerCountStr = fingerCountDirection
else:
fingerCountStr = fingerCountDirection[:fingerCountDirection.find(' ')]
fingerCount = -1;
try:
fingerCount = int(fingerCountStr)
except ValueError:
pass
return fingerCount
# This function gets the direction of the swipe
def getDirection(line):
parts = line.split('\t')[2].split(' ')
direction = [0,0]
yDirectionInNextPart = False
for part in parts:
if yDirectionInNextPart and len(part) != 0:
yDirectionInNextPart = False
direction[1] = float(part)
break;
if '/' in part:
directionStrings = part.split('/')
direction[0] = float(directionStrings[0])
if len(directionStrings[1]) == 0:
yDirectionInNextPart = True
else:
direction[1] = float(directionStrings[1])
break;
return direction
# run libinput-debug-events
# the --device INPUT_DEVICE argument lets libinput-debug-events print only events of this device
# stdbuf -oL is very important, because the standard libc functions always buffer if the output is written
# into a pipe. stdbuf -oL prevents this.
proc = subprocess.Popen(['stdbuf', '-oL', '--', 'libinput-debug-events', '--device', INPUT_DEVICE], stdout=subprocess.PIPE)
# get the output of libinput-debug-events forever
while True:
# get a line of libinput-debug-events
output = proc.stdout.readline().decode('ascii').rstrip()
if output == '' and proc.poll() is not None:
break #stop if libinput-debug-events stops, but this should not happen
if output:
# run the actions for the different events
if 'GESTURE_SWIPE_BEGIN' in output:
fingerCount = getFingerCount(output)
if fingerCount == FINGER_COUNT:
subprocess.call(['xdotool', 'keydown', KEYCOMBO, 'mousedown', '1'])
if 'GESTURE_SWIPE_UPDATE' in output:
fingerCount = getFingerCount(output)
direction = getDirection(output)
if fingerCount == FINGER_COUNT:
if ONLY_X:
direction[1] = 0
subprocess.call(['xdotool', 'mousemove_relative', '--', str(int(direction[0]*SPEED)), str(int(direction[1]*SPEED))])
if 'GESTURE_SWIPE_END' in output:
fingerCount = getFingerCount(output)
if fingerCount == FINGER_COUNT:
subprocess.call(['xdotool', 'mouseup', '1', 'keyup', KEYCOMBO])

Result

And it works \o/


You can download the code here: swipe.py

I will set up a github repo in the near future and make the script configurable with a config file and maybe some more things.