aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Florian Franzen <Florian.Franzen@gmail.com>2022-04-23 10:27:47 +0200
committerLibravatar Simon Ser <contact@emersion.fr>2022-05-30 12:20:43 +0200
commitcab2189aa64d04ba79dc2cbf19400435b47cdbd2 (patch)
tree450ac51fbc75c73ed1dc6728bc05b08366ace785
parentAdd a Hindi (हिन्दी) translation to the README (diff)
downloadsway-cab2189aa64d04ba79dc2cbf19400435b47cdbd2.tar.gz
sway-cab2189aa64d04ba79dc2cbf19400435b47cdbd2.tar.zst
sway-cab2189aa64d04ba79dc2cbf19400435b47cdbd2.zip
sway: add bindgesture command
Co-authored-by: Michael Weiser <michael.weiser@gmx.de>
-rw-r--r--common/gesture.c350
-rw-r--r--common/meson.build1
-rw-r--r--include/gesture.h104
-rw-r--r--include/stringop.h1
-rw-r--r--include/sway/commands.h2
-rw-r--r--include/sway/config.h18
-rw-r--r--include/sway/input/cursor.h4
-rw-r--r--include/sway/input/seat.h35
-rw-r--r--sway/commands.c3
-rw-r--r--sway/commands/gesture.c166
-rw-r--r--sway/commands/mode.c3
-rw-r--r--sway/config.c7
-rw-r--r--sway/input/cursor.c89
-rw-r--r--sway/input/seat.c56
-rw-r--r--sway/input/seatop_default.c310
-rw-r--r--sway/meson.build1
-rw-r--r--sway/sway.5.scd61
17 files changed, 1156 insertions, 55 deletions
diff --git a/common/gesture.c b/common/gesture.c
new file mode 100644
index 00000000..8c2efe99
--- /dev/null
+++ b/common/gesture.c
@@ -0,0 +1,350 @@
1#define _POSIX_C_SOURCE 200809L
2#include "gesture.h"
3
4#include <math.h>
5#include <stdarg.h>
6#include <stdio.h>
7#include <stdlib.h>
8#include <string.h>
9#include "list.h"
10#include "log.h"
11#include "stringop.h"
12
13const uint8_t GESTURE_FINGERS_ANY = 0;
14
15// Helper to easily allocate and format string
16static char *strformat(const char *format, ...) {
17 va_list args;
18 va_start(args, format);
19 int length = vsnprintf(NULL, 0, format, args) + 1;
20 va_end(args);
21
22 char *result = malloc(length);
23 if (result) {
24 va_start(args, format);
25 vsnprintf(result, length, format, args);
26 va_end(args);
27 }
28
29 return result;
30}
31
32char *gesture_parse(const char *input, struct gesture *output) {
33 // Clear output in case of failure
34 output->type = GESTURE_TYPE_NONE;
35 output->fingers = GESTURE_FINGERS_ANY;
36 output->directions = GESTURE_DIRECTION_NONE;
37
38 // Split input type, fingers and directions
39 list_t *split = split_string(input, ":");
40 if (split->length < 1 || split->length > 3) {
41 return strformat(
42 "expected <gesture>[:<fingers>][:direction], got %s",
43 input);
44 }
45
46 // Parse gesture type
47 if (strcmp(split->items[0], "hold") == 0) {
48 output->type = GESTURE_TYPE_HOLD;
49 } else if (strcmp(split->items[0], "pinch") == 0) {
50 output->type = GESTURE_TYPE_PINCH;
51 } else if (strcmp(split->items[0], "swipe") == 0) {
52 output->type = GESTURE_TYPE_SWIPE;
53 } else {
54 return strformat("expected hold|pinch|swipe, got %s",
55 split->items[0]);
56 }
57
58 // Parse optional arguments
59 if (split->length > 1) {
60 char *next = split->items[1];
61
62 // Try to parse as finger count (1-9)
63 if (strlen(next) == 1 && '1' <= next[0] && next[0] <= '9') {
64 output->fingers = atoi(next);
65
66 // Move to next if available
67 next = split->length == 3 ? split->items[2] : NULL;
68 } else if (split->length == 3) {
69 // Fail here if argument can only be finger count
70 return strformat("expected 1-9, got %s", next);
71 }
72
73 // If there is an argument left, try to parse as direction
74 if (next) {
75 list_t *directions = split_string(next, "+");
76
77 for (int i = 0; i < directions->length; ++i) {
78 const char *item = directions->items[i];
79 if (strcmp(item, "any") == 0) {
80 continue;
81 } else if (strcmp(item, "up") == 0) {
82 output->directions |= GESTURE_DIRECTION_UP;
83 } else if (strcmp(item, "down") == 0) {
84 output->directions |= GESTURE_DIRECTION_DOWN;
85 } else if (strcmp(item, "left") == 0) {
86 output->directions |= GESTURE_DIRECTION_LEFT;
87 } else if (strcmp(item, "right") == 0) {
88 output->directions |= GESTURE_DIRECTION_RIGHT;
89 } else if (strcmp(item, "inward") == 0) {
90 output->directions |= GESTURE_DIRECTION_INWARD;
91 } else if (strcmp(item, "outward") == 0) {
92 output->directions |= GESTURE_DIRECTION_OUTWARD;
93 } else if (strcmp(item, "clockwise") == 0) {
94 output->directions |= GESTURE_DIRECTION_CLOCKWISE;
95 } else if (strcmp(item, "counterclockwise") == 0) {
96 output->directions |= GESTURE_DIRECTION_COUNTERCLOCKWISE;
97 } else {
98 return strformat("expected direction, got %s", item);
99 }
100 }
101 list_free_items_and_destroy(directions);
102 }
103 } // if optional args
104
105 list_free_items_and_destroy(split);
106
107 return NULL;
108}
109
110const char *gesture_type_string(enum gesture_type type) {
111 switch (type) {
112 case GESTURE_TYPE_NONE:
113 return "none";
114 case GESTURE_TYPE_HOLD:
115 return "hold";
116 case GESTURE_TYPE_PINCH:
117 return "pinch";
118 case GESTURE_TYPE_SWIPE:
119 return "swipe";
120 }
121
122 return NULL;
123}
124
125const char *gesture_direction_string(enum gesture_direction direction) {
126 switch (direction) {
127 case GESTURE_DIRECTION_NONE:
128 return "none";
129 case GESTURE_DIRECTION_UP:
130 return "up";
131 case GESTURE_DIRECTION_DOWN:
132 return "down";
133 case GESTURE_DIRECTION_LEFT:
134 return "left";
135 case GESTURE_DIRECTION_RIGHT:
136 return "right";
137 case GESTURE_DIRECTION_INWARD:
138 return "inward";
139 case GESTURE_DIRECTION_OUTWARD:
140 return "outward";
141 case GESTURE_DIRECTION_CLOCKWISE:
142 return "clockwise";
143 case GESTURE_DIRECTION_COUNTERCLOCKWISE:
144 return "counterclockwise";
145 }
146
147 return NULL;
148}
149
150// Helper to turn combination of directions flags into string representation.
151static char *gesture_directions_to_string(uint32_t directions) {
152 char *result = NULL;
153
154 for (uint8_t bit = 0; bit < 32; bit++) {
155 uint32_t masked = directions & (1 << bit);
156 if (masked) {
157 const char *name = gesture_direction_string(masked);
158
159 if (!name) {
160 name = "unknown";
161 }
162
163 if (!result) {
164 result = strdup(name);
165 } else {
166 char *new = strformat("%s+%s", result, name);
167 free(result);
168 result = new;
169 }
170 }
171 }
172
173 if(!result) {
174 return strdup("any");
175 }
176
177 return result;
178}
179
180char *gesture_to_string(struct gesture *gesture) {
181 char *directions = gesture_directions_to_string(gesture->directions);
182 char *result = strformat("%s:%u:%s",
183 gesture_type_string(gesture->type),
184 gesture->fingers, directions);
185 free(directions);
186 return result;
187}
188
189bool gesture_check(struct gesture *target, enum gesture_type type, uint8_t fingers) {
190 // Check that gesture type matches
191 if (target->type != type) {
192 return false;
193 }
194
195 // Check that finger count matches
196 if (target->fingers != GESTURE_FINGERS_ANY && target->fingers != fingers) {
197 return false;
198 }
199
200 return true;
201}
202
203bool gesture_match(struct gesture *target, struct gesture *to_match, bool exact) {
204 // Check type and fingers
205 if (!gesture_check(target, to_match->type, to_match->fingers)) {
206 return false;
207 }
208
209 // Enforce exact matches ...
210 if (exact && target->directions != to_match->directions) {
211 return false;
212 }
213
214 // ... or ensure all target directions are matched
215 return (target->directions & to_match->directions) == target->directions;
216}
217
218bool gesture_equal(struct gesture *a, struct gesture *b) {
219 return a->type == b->type
220 && a->fingers == b->fingers
221 && a->directions == b->directions;
222}
223
224// Return count of set bits in directions bit field.
225static uint8_t gesture_directions_count(uint32_t directions) {
226 uint8_t count = 0;
227 for (; directions; directions >>= 1) {
228 count += directions & 1;
229 }
230 return count;
231}
232
233// Compare direction bit count of two direction.
234static int8_t gesture_directions_compare(uint32_t a, uint32_t b) {
235 return gesture_directions_count(a) - gesture_directions_count(b);
236}
237
238// Compare two direction based on their direction bit count
239int8_t gesture_compare(struct gesture *a, struct gesture *b) {
240 return gesture_directions_compare(a->directions, b->directions);
241}
242
243void gesture_tracker_begin(struct gesture_tracker *tracker,
244 enum gesture_type type, uint8_t fingers) {
245 tracker->type = type;
246 tracker->fingers = fingers;
247
248 tracker->dx = 0.0;
249 tracker->dy = 0.0;
250 tracker->scale = 1.0;
251 tracker->rotation = 0.0;
252
253 sway_log(SWAY_DEBUG, "begin tracking %s:%u:? gesture",
254 gesture_type_string(type), fingers);
255}
256
257bool gesture_tracker_check(struct gesture_tracker *tracker, enum gesture_type type) {
258 return tracker->type == type;
259}
260
261void gesture_tracker_update(struct gesture_tracker *tracker,
262 double dx, double dy, double scale, double rotation) {
263 if (tracker->type == GESTURE_TYPE_HOLD) {
264 sway_assert(false, "hold does not update.");
265 return;
266 }
267
268 tracker->dx += dx;
269 tracker->dy += dy;
270
271 if (tracker->type == GESTURE_TYPE_PINCH) {
272 tracker->scale = scale;
273 tracker->rotation += rotation;
274 }
275
276 sway_log(SWAY_DEBUG, "update tracking %s:%u:? gesture: %f %f %f %f",
277 gesture_type_string(tracker->type),
278 tracker->fingers,
279 tracker->dx, tracker->dy,
280 tracker->scale, tracker->rotation);
281}
282
283void gesture_tracker_cancel(struct gesture_tracker *tracker) {
284 sway_log(SWAY_DEBUG, "cancel tracking %s:%u:? gesture",
285 gesture_type_string(tracker->type), tracker->fingers);
286
287 tracker->type = GESTURE_TYPE_NONE;
288}
289
290struct gesture *gesture_tracker_end(struct gesture_tracker *tracker) {
291 struct gesture *result = calloc(1, sizeof(struct gesture));
292
293 // Ignore gesture under some threshold
294 // TODO: Make configurable
295 const double min_rotation = 5;
296 const double min_scale_delta = 0.1;
297
298 // Determine direction
299 switch(tracker->type) {
300 // Gestures with scale and rotation
301 case GESTURE_TYPE_PINCH:
302 if (tracker->rotation > min_rotation) {
303 result->directions |= GESTURE_DIRECTION_CLOCKWISE;
304 }
305 if (tracker->rotation < -min_rotation) {
306 result->directions |= GESTURE_DIRECTION_COUNTERCLOCKWISE;
307 }
308
309 if (tracker->scale > (1.0 + min_scale_delta)) {
310 result->directions |= GESTURE_DIRECTION_OUTWARD;
311 }
312 if (tracker->scale < (1.0 - min_scale_delta)) {
313 result->directions |= GESTURE_DIRECTION_INWARD;
314 }
315 __attribute__ ((fallthrough));
316 // Gestures with dx and dy
317 case GESTURE_TYPE_SWIPE:
318 if (fabs(tracker->dx) > fabs(tracker->dy)) {
319 if (tracker->dx > 0) {
320 result->directions |= GESTURE_DIRECTION_RIGHT;
321 } else {
322 result->directions |= GESTURE_DIRECTION_LEFT;
323 }
324 } else {
325 if (tracker->dy > 0) {
326 result->directions |= GESTURE_DIRECTION_DOWN;
327 } else {
328 result->directions |= GESTURE_DIRECTION_UP;
329 }
330 }
331 // Gesture without any direction
332 case GESTURE_TYPE_HOLD:
333 break;
334 // Not tracking any gesture
335 case GESTURE_TYPE_NONE:
336 sway_assert(false, "Not tracking any gesture.");
337 return result;
338 }
339
340 result->type = tracker->type;
341 result->fingers = tracker->fingers;
342
343 char *description = gesture_to_string(result);
344 sway_log(SWAY_DEBUG, "end tracking gesture: %s", description);
345 free(description);
346
347 tracker->type = GESTURE_TYPE_NONE;
348
349 return result;
350}
diff --git a/common/meson.build b/common/meson.build
index c653dd72..3756075a 100644
--- a/common/meson.build
+++ b/common/meson.build
@@ -3,6 +3,7 @@ lib_sway_common = static_library(
3 files( 3 files(
4 'background-image.c', 4 'background-image.c',
5 'cairo.c', 5 'cairo.c',
6 'gesture.c',
6 'ipc-client.c', 7 'ipc-client.c',
7 'log.c', 8 'log.c',
8 'loop.c', 9 'loop.c',
diff --git a/include/gesture.h b/include/gesture.h
new file mode 100644
index 00000000..9c6b0f91
--- /dev/null
+++ b/include/gesture.h
@@ -0,0 +1,104 @@
1#ifndef _SWAY_GESTURE_H
2#define _SWAY_GESTURE_H
3
4#include <stdbool.h>
5#include <stdint.h>
6
7/**
8 * A gesture type used in binding.
9 */
10enum gesture_type {
11 GESTURE_TYPE_NONE = 0,
12 GESTURE_TYPE_HOLD,
13 GESTURE_TYPE_PINCH,
14 GESTURE_TYPE_SWIPE,
15};
16
17// Turns single type enum value to constant string representation.
18const char *gesture_type_string(enum gesture_type direction);
19
20// Value to use to accept any finger count
21extern const uint8_t GESTURE_FINGERS_ANY;
22
23/**
24 * A gesture direction used in binding.
25 */
26enum gesture_direction {
27 GESTURE_DIRECTION_NONE = 0,
28 // Directions based on delta x and y
29 GESTURE_DIRECTION_UP = 1 << 0,
30 GESTURE_DIRECTION_DOWN = 1 << 1,
31 GESTURE_DIRECTION_LEFT = 1 << 2,
32 GESTURE_DIRECTION_RIGHT = 1 << 3,
33 // Directions based on scale
34 GESTURE_DIRECTION_INWARD = 1 << 4,
35 GESTURE_DIRECTION_OUTWARD = 1 << 5,
36 // Directions based on rotation
37 GESTURE_DIRECTION_CLOCKWISE = 1 << 6,
38 GESTURE_DIRECTION_COUNTERCLOCKWISE = 1 << 7,
39};
40
41// Turns single direction enum value to constant string representation.
42const char *gesture_direction_string(enum gesture_direction direction);
43
44/**
45 * Struct representing a pointer gesture
46 */
47struct gesture {
48 enum gesture_type type;
49 uint8_t fingers;
50 uint32_t directions;
51};
52
53/**
54 * Parses gesture from <gesture>[:<fingers>][:<directions>] string.
55 *
56 * Return NULL on success, otherwise error message string
57 */
58char *gesture_parse(const char *input, struct gesture *output);
59
60// Turns gesture into string representation
61char *gesture_to_string(struct gesture *gesture);
62
63// Check if gesture is of certain type and finger count.
64bool gesture_check(struct gesture *target,
65 enum gesture_type type, uint8_t fingers);
66
67// Check if a gesture target/binding is match by other gesture/input
68bool gesture_match(struct gesture *target,
69 struct gesture *to_match, bool exact);
70
71// Returns true if gesture are exactly the same
72bool gesture_equal(struct gesture *a, struct gesture *b);
73
74// Compare distance between two matched target gestures.
75int8_t gesture_compare(struct gesture *a, struct gesture *b);
76
77// Small helper struct to track gestures over time
78struct gesture_tracker {
79 enum gesture_type type;
80 uint8_t fingers;
81 double dx, dy;
82 double scale;
83 double rotation;
84};
85
86// Begin gesture tracking
87void gesture_tracker_begin(struct gesture_tracker *tracker,
88 enum gesture_type type, uint8_t fingers);
89
90// Check if the provides type is currently being tracked
91bool gesture_tracker_check(struct gesture_tracker *tracker,
92 enum gesture_type type);
93
94// Update gesture track with new data point
95void gesture_tracker_update(struct gesture_tracker *tracker, double dx,
96 double dy, double scale, double rotation);
97
98// Reset tracker
99void gesture_tracker_cancel(struct gesture_tracker *tracker);
100
101// Reset tracker and return gesture tracked
102struct gesture *gesture_tracker_end(struct gesture_tracker *tracker);
103
104#endif
diff --git a/include/stringop.h b/include/stringop.h
index 8d7089e9..b29f59b2 100644
--- a/include/stringop.h
+++ b/include/stringop.h
@@ -2,6 +2,7 @@
2#define _SWAY_STRINGOP_H 2#define _SWAY_STRINGOP_H
3 3
4#include <stdbool.h> 4#include <stdbool.h>
5#include <stddef.h>
5#include "list.h" 6#include "list.h"
6 7
7void strip_whitespace(char *str); 8void strip_whitespace(char *str);
diff --git a/include/sway/commands.h b/include/sway/commands.h
index 2746ef28..5f71a79d 100644
--- a/include/sway/commands.h
+++ b/include/sway/commands.h
@@ -106,6 +106,7 @@ sway_cmd cmd_exec_process;
106sway_cmd cmd_assign; 106sway_cmd cmd_assign;
107sway_cmd cmd_bar; 107sway_cmd cmd_bar;
108sway_cmd cmd_bindcode; 108sway_cmd cmd_bindcode;
109sway_cmd cmd_bindgesture;
109sway_cmd cmd_bindswitch; 110sway_cmd cmd_bindswitch;
110sway_cmd cmd_bindsym; 111sway_cmd cmd_bindsym;
111sway_cmd cmd_border; 112sway_cmd cmd_border;
@@ -191,6 +192,7 @@ sway_cmd cmd_titlebar_border_thickness;
191sway_cmd cmd_titlebar_padding; 192sway_cmd cmd_titlebar_padding;
192sway_cmd cmd_unbindcode; 193sway_cmd cmd_unbindcode;
193sway_cmd cmd_unbindswitch; 194sway_cmd cmd_unbindswitch;
195sway_cmd cmd_unbindgesture;
194sway_cmd cmd_unbindsym; 196sway_cmd cmd_unbindsym;
195sway_cmd cmd_unmark; 197sway_cmd cmd_unmark;
196sway_cmd cmd_urgent; 198sway_cmd cmd_urgent;
diff --git a/include/sway/config.h b/include/sway/config.h
index 2e24c3ae..05678c33 100644
--- a/include/sway/config.h
+++ b/include/sway/config.h
@@ -10,6 +10,7 @@
10#include <xkbcommon/xkbcommon.h> 10#include <xkbcommon/xkbcommon.h>
11#include <xf86drmMode.h> 11#include <xf86drmMode.h>
12#include "../include/config.h" 12#include "../include/config.h"
13#include "gesture.h"
13#include "list.h" 14#include "list.h"
14#include "swaynag.h" 15#include "swaynag.h"
15#include "tree/container.h" 16#include "tree/container.h"
@@ -32,7 +33,8 @@ enum binding_input_type {
32 BINDING_KEYSYM, 33 BINDING_KEYSYM,
33 BINDING_MOUSECODE, 34 BINDING_MOUSECODE,
34 BINDING_MOUSESYM, 35 BINDING_MOUSESYM,
35 BINDING_SWITCH 36 BINDING_SWITCH, // dummy, only used to call seat_execute_command
37 BINDING_GESTURE // dummy, only used to call seat_execute_command
36}; 38};
37 39
38enum binding_flags { 40enum binding_flags {
@@ -45,6 +47,7 @@ enum binding_flags {
45 BINDING_RELOAD = 1 << 6, // switch only; (re)trigger binding on reload 47 BINDING_RELOAD = 1 << 6, // switch only; (re)trigger binding on reload
46 BINDING_INHIBITED = 1 << 7, // keyboard only: ignore shortcut inhibitor 48 BINDING_INHIBITED = 1 << 7, // keyboard only: ignore shortcut inhibitor
47 BINDING_NOREPEAT = 1 << 8, // keyboard only; do not trigger when repeating a held key 49 BINDING_NOREPEAT = 1 << 8, // keyboard only; do not trigger when repeating a held key
50 BINDING_EXACT = 1 << 9, // gesture only; only trigger on exact match
48}; 51};
49 52
50/** 53/**
@@ -79,6 +82,16 @@ struct sway_switch_binding {
79}; 82};
80 83
81/** 84/**
85 * A gesture binding and an associated command.
86 */
87struct sway_gesture_binding {
88 char *input;
89 uint32_t flags;
90 struct gesture gesture;
91 char *command;
92};
93
94/**
82 * Focus on window activation. 95 * Focus on window activation.
83 */ 96 */
84enum sway_fowa { 97enum sway_fowa {
@@ -97,6 +110,7 @@ struct sway_mode {
97 list_t *keycode_bindings; 110 list_t *keycode_bindings;
98 list_t *mouse_bindings; 111 list_t *mouse_bindings;
99 list_t *switch_bindings; 112 list_t *switch_bindings;
113 list_t *gesture_bindings;
100 bool pango; 114 bool pango;
101}; 115};
102 116
@@ -689,6 +703,8 @@ void free_sway_binding(struct sway_binding *sb);
689 703
690void free_switch_binding(struct sway_switch_binding *binding); 704void free_switch_binding(struct sway_switch_binding *binding);
691 705
706void free_gesture_binding(struct sway_gesture_binding *binding);
707
692void seat_execute_command(struct sway_seat *seat, struct sway_binding *binding); 708void seat_execute_command(struct sway_seat *seat, struct sway_binding *binding);
693 709
694void load_swaybar(struct bar_config *bar); 710void load_swaybar(struct bar_config *bar);
diff --git a/include/sway/input/cursor.h b/include/sway/input/cursor.h
index 3a71a35f..8a2898dd 100644
--- a/include/sway/input/cursor.h
+++ b/include/sway/input/cursor.h
@@ -36,14 +36,14 @@ struct sway_cursor {
36 bool active_confine_requires_warp; 36 bool active_confine_requires_warp;
37 37
38 struct wlr_pointer_gestures_v1 *pointer_gestures; 38 struct wlr_pointer_gestures_v1 *pointer_gestures;
39 struct wl_listener hold_begin;
40 struct wl_listener hold_end;
39 struct wl_listener pinch_begin; 41 struct wl_listener pinch_begin;
40 struct wl_listener pinch_update; 42 struct wl_listener pinch_update;
41 struct wl_listener pinch_end; 43 struct wl_listener pinch_end;
42 struct wl_listener swipe_begin; 44 struct wl_listener swipe_begin;
43 struct wl_listener swipe_update; 45 struct wl_listener swipe_update;
44 struct wl_listener swipe_end; 46 struct wl_listener swipe_end;
45 struct wl_listener hold_begin;
46 struct wl_listener hold_end;
47 47
48 struct wl_listener motion; 48 struct wl_listener motion;
49 struct wl_listener motion_absolute; 49 struct wl_listener motion_absolute;
diff --git a/include/sway/input/seat.h b/include/sway/input/seat.h
index 47726159..c2041742 100644
--- a/include/sway/input/seat.h
+++ b/include/sway/input/seat.h
@@ -19,6 +19,22 @@ struct sway_seatop_impl {
19 void (*pointer_motion)(struct sway_seat *seat, uint32_t time_msec); 19 void (*pointer_motion)(struct sway_seat *seat, uint32_t time_msec);
20 void (*pointer_axis)(struct sway_seat *seat, 20 void (*pointer_axis)(struct sway_seat *seat,
21 struct wlr_pointer_axis_event *event); 21 struct wlr_pointer_axis_event *event);
22 void (*hold_begin)(struct sway_seat *seat,
23 struct wlr_pointer_hold_begin_event *event);
24 void (*hold_end)(struct sway_seat *seat,
25 struct wlr_pointer_hold_end_event *event);
26 void (*pinch_begin)(struct sway_seat *seat,
27 struct wlr_pointer_pinch_begin_event *event);
28 void (*pinch_update)(struct sway_seat *seat,
29 struct wlr_pointer_pinch_update_event *event);
30 void (*pinch_end)(struct sway_seat *seat,
31 struct wlr_pointer_pinch_end_event *event);
32 void (*swipe_begin)(struct sway_seat *seat,
33 struct wlr_pointer_swipe_begin_event *event);
34 void (*swipe_update)(struct sway_seat *seat,
35 struct wlr_pointer_swipe_update_event *event);
36 void (*swipe_end)(struct sway_seat *seat,
37 struct wlr_pointer_swipe_end_event *event);
22 void (*rebase)(struct sway_seat *seat, uint32_t time_msec); 38 void (*rebase)(struct sway_seat *seat, uint32_t time_msec);
23 void (*tablet_tool_motion)(struct sway_seat *seat, 39 void (*tablet_tool_motion)(struct sway_seat *seat,
24 struct sway_tablet_tool *tool, uint32_t time_msec); 40 struct sway_tablet_tool *tool, uint32_t time_msec);
@@ -287,6 +303,25 @@ void seatop_tablet_tool_tip(struct sway_seat *seat,
287void seatop_tablet_tool_motion(struct sway_seat *seat, 303void seatop_tablet_tool_motion(struct sway_seat *seat,
288 struct sway_tablet_tool *tool, uint32_t time_msec); 304 struct sway_tablet_tool *tool, uint32_t time_msec);
289 305
306void seatop_hold_begin(struct sway_seat *seat,
307 struct wlr_pointer_hold_begin_event *event);
308void seatop_hold_end(struct sway_seat *seat,
309 struct wlr_pointer_hold_end_event *event);
310
311void seatop_pinch_begin(struct sway_seat *seat,
312 struct wlr_pointer_pinch_begin_event *event);
313void seatop_pinch_update(struct sway_seat *seat,
314 struct wlr_pointer_pinch_update_event *event);
315void seatop_pinch_end(struct sway_seat *seat,
316 struct wlr_pointer_pinch_end_event *event);
317
318void seatop_swipe_begin(struct sway_seat *seat,
319 struct wlr_pointer_swipe_begin_event *event);
320void seatop_swipe_update(struct sway_seat *seat,
321 struct wlr_pointer_swipe_update_event *event);
322void seatop_swipe_end(struct sway_seat *seat,
323 struct wlr_pointer_swipe_end_event *event);
324
290void seatop_rebase(struct sway_seat *seat, uint32_t time_msec); 325void seatop_rebase(struct sway_seat *seat, uint32_t time_msec);
291 326
292/** 327/**
diff --git a/sway/commands.c b/sway/commands.c
index 5a1fd32e..0ced71ec 100644
--- a/sway/commands.c
+++ b/sway/commands.c
@@ -46,6 +46,7 @@ static const struct cmd_handler handlers[] = {
46 { "assign", cmd_assign }, 46 { "assign", cmd_assign },
47 { "bar", cmd_bar }, 47 { "bar", cmd_bar },
48 { "bindcode", cmd_bindcode }, 48 { "bindcode", cmd_bindcode },
49 { "bindgesture", cmd_bindgesture },
49 { "bindswitch", cmd_bindswitch }, 50 { "bindswitch", cmd_bindswitch },
50 { "bindsym", cmd_bindsym }, 51 { "bindsym", cmd_bindsym },
51 { "client.background", cmd_client_noop }, 52 { "client.background", cmd_client_noop },
@@ -92,6 +93,7 @@ static const struct cmd_handler handlers[] = {
92 { "titlebar_border_thickness", cmd_titlebar_border_thickness }, 93 { "titlebar_border_thickness", cmd_titlebar_border_thickness },
93 { "titlebar_padding", cmd_titlebar_padding }, 94 { "titlebar_padding", cmd_titlebar_padding },
94 { "unbindcode", cmd_unbindcode }, 95 { "unbindcode", cmd_unbindcode },
96 { "unbindgesture", cmd_unbindgesture },
95 { "unbindswitch", cmd_unbindswitch }, 97 { "unbindswitch", cmd_unbindswitch },
96 { "unbindsym", cmd_unbindsym }, 98 { "unbindsym", cmd_unbindsym },
97 { "workspace", cmd_workspace }, 99 { "workspace", cmd_workspace },
@@ -407,6 +409,7 @@ struct cmd_results *config_command(char *exec, char **new_block) {
407 && handler->handle != cmd_bindsym 409 && handler->handle != cmd_bindsym
408 && handler->handle != cmd_bindcode 410 && handler->handle != cmd_bindcode
409 && handler->handle != cmd_bindswitch 411 && handler->handle != cmd_bindswitch
412 && handler->handle != cmd_bindgesture
410 && handler->handle != cmd_set 413 && handler->handle != cmd_set
411 && handler->handle != cmd_for_window 414 && handler->handle != cmd_for_window
412 && (*argv[i] == '\"' || *argv[i] == '\'')) { 415 && (*argv[i] == '\"' || *argv[i] == '\'')) {
diff --git a/sway/commands/gesture.c b/sway/commands/gesture.c
new file mode 100644
index 00000000..d4442cc3
--- /dev/null
+++ b/sway/commands/gesture.c
@@ -0,0 +1,166 @@
1#define _POSIX_C_SOURCE 200809L
2#include "sway/config.h"
3
4#include "gesture.h"
5#include "log.h"
6#include "stringop.h"
7#include "sway/commands.h"
8
9void free_gesture_binding(struct sway_gesture_binding *binding) {
10 if (!binding) {
11 return;
12 }
13 free(binding->input);
14 free(binding->command);
15 free(binding);
16}
17
18/**
19 * Returns true if the bindings have the same gesture type, direction, etc
20 */
21static bool binding_gesture_equal(struct sway_gesture_binding *binding_a,
22 struct sway_gesture_binding *binding_b) {
23 if (strcmp(binding_a->input, binding_b->input) != 0) {
24 return false;
25 }
26
27 if (!gesture_equal(&binding_a->gesture, &binding_b->gesture)) {
28 return false;
29 }
30
31 if ((binding_a->flags & BINDING_EXACT) !=
32 (binding_b->flags & BINDING_EXACT)) {
33 return false;
34 }
35 return true;
36}
37
38/**
39 * Add gesture binding to config
40 */
41static struct cmd_results *gesture_binding_add(
42 struct sway_gesture_binding *binding,
43 const char *gesturecombo, bool warn) {
44 list_t *mode_bindings = config->current_mode->gesture_bindings;
45 // overwrite the binding if it already exists
46 bool overwritten = false;
47 for (int i = 0; i < mode_bindings->length; ++i) {
48 struct sway_gesture_binding *config_binding = mode_bindings->items[i];
49 if (binding_gesture_equal(binding, config_binding)) {
50 sway_log(SWAY_INFO, "Overwriting binding '%s' to `%s` from `%s`",
51 gesturecombo, binding->command, config_binding->command);
52 if (warn) {
53 config_add_swaynag_warning("Overwriting binding"
54 "'%s' to `%s` from `%s`",
55 gesturecombo, binding->command,
56 config_binding->command);
57 }
58 free_gesture_binding(config_binding);
59 mode_bindings->items[i] = binding;
60 overwritten = true;
61 }
62 }
63
64 if (!overwritten) {
65 list_add(mode_bindings, binding);
66 sway_log(SWAY_DEBUG, "bindgesture - Bound %s to command `%s`",
67 gesturecombo, binding->command);
68 }
69
70 return cmd_results_new(CMD_SUCCESS, NULL);
71}
72
73/**
74 * Remove gesture binding from config
75 */
76static struct cmd_results *gesture_binding_remove(
77 struct sway_gesture_binding *binding, const char *gesturecombo) {
78 list_t *mode_bindings = config->current_mode->gesture_bindings;
79 for (int i = 0; i < mode_bindings->length; ++i) {
80 struct sway_gesture_binding *config_binding = mode_bindings->items[i];
81 if (binding_gesture_equal(binding, config_binding)) {
82 free_gesture_binding(config_binding);
83 free_gesture_binding(binding);
84 list_del(mode_bindings, i);
85 sway_log(SWAY_DEBUG, "unbindgesture - Unbound %s gesture",
86 gesturecombo);
87 return cmd_results_new(CMD_SUCCESS, NULL);
88 }
89 }
90
91 free_gesture_binding(binding);
92 return cmd_results_new(CMD_FAILURE, "Could not find gesture binding `%s`",
93 gesturecombo);
94}
95
96/**
97 * Parse and execute bindgesture or unbindgesture command.
98 */
99static struct cmd_results *cmd_bind_or_unbind_gesture(int argc, char **argv, bool unbind) {
100 int minargs = 2;
101 char *bindtype = "bindgesture";
102 if (unbind) {
103 minargs--;
104 bindtype = "unbindgesture";
105 }
106
107 struct cmd_results *error = NULL;
108 if ((error = checkarg(argc, bindtype, EXPECTED_AT_LEAST, minargs))) {
109 return error;
110 }
111 struct sway_gesture_binding *binding = calloc(1, sizeof(struct sway_gesture_binding));
112 if (!binding) {
113 return cmd_results_new(CMD_FAILURE, "Unable to allocate binding");
114 }
115 binding->input = strdup("*");
116
117 bool warn = true;
118
119 // Handle flags
120 while (argc > 0) {
121 if (strcmp("--exact", argv[0]) == 0) {
122 binding->flags |= BINDING_EXACT;
123 } else if (strcmp("--no-warn", argv[0]) == 0) {
124 warn = false;
125 } else if (strncmp("--input-device=", argv[0],
126 strlen("--input-device=")) == 0) {
127 free(binding->input);
128 binding->input = strdup(argv[0] + strlen("--input-device="));
129 } else {
130 break;
131 }
132 argv++;
133 argc--;
134 }
135
136 if (argc < minargs) {
137 free(binding);
138 return cmd_results_new(CMD_FAILURE,
139 "Invalid %s command (expected at least %d "
140 "non-option arguments, got %d)", bindtype, minargs, argc);
141 }
142
143 char* errmsg = NULL;
144 if ((errmsg = gesture_parse(argv[0], &binding->gesture))) {
145 free(binding);
146 struct cmd_results *final = cmd_results_new(CMD_FAILURE,
147 "Invalid %s command (%s)",
148 bindtype, errmsg);
149 free(errmsg);
150 return final;
151 }
152
153 if (unbind) {
154 return gesture_binding_remove(binding, argv[0]);
155 }
156 binding->command = join_args(argv + 1, argc - 1);
157 return gesture_binding_add(binding, argv[0], warn);
158}
159
160struct cmd_results *cmd_bindgesture(int argc, char **argv) {
161 return cmd_bind_or_unbind_gesture(argc, argv, false);
162}
163
164struct cmd_results *cmd_unbindgesture(int argc, char **argv) {
165 return cmd_bind_or_unbind_gesture(argc, argv, true);
166}
diff --git a/sway/commands/mode.c b/sway/commands/mode.c
index e23e4ee4..7263efcb 100644
--- a/sway/commands/mode.c
+++ b/sway/commands/mode.c
@@ -11,10 +11,12 @@
11// Must be in order for the bsearch 11// Must be in order for the bsearch
12static const struct cmd_handler mode_handlers[] = { 12static const struct cmd_handler mode_handlers[] = {
13 { "bindcode", cmd_bindcode }, 13 { "bindcode", cmd_bindcode },
14 { "bindgesture", cmd_bindgesture },
14 { "bindswitch", cmd_bindswitch }, 15 { "bindswitch", cmd_bindswitch },
15 { "bindsym", cmd_bindsym }, 16 { "bindsym", cmd_bindsym },
16 { "set", cmd_set }, 17 { "set", cmd_set },
17 { "unbindcode", cmd_unbindcode }, 18 { "unbindcode", cmd_unbindcode },
19 { "unbindgesture", cmd_unbindgesture },
18 { "unbindswitch", cmd_unbindswitch }, 20 { "unbindswitch", cmd_unbindswitch },
19 { "unbindsym", cmd_unbindsym }, 21 { "unbindsym", cmd_unbindsym },
20}; 22};
@@ -59,6 +61,7 @@ struct cmd_results *cmd_mode(int argc, char **argv) {
59 mode->keycode_bindings = create_list(); 61 mode->keycode_bindings = create_list();
60 mode->mouse_bindings = create_list(); 62 mode->mouse_bindings = create_list();
61 mode->switch_bindings = create_list(); 63 mode->switch_bindings = create_list();
64 mode->gesture_bindings = create_list();
62 mode->pango = pango; 65 mode->pango = pango;
63 list_add(config->modes, mode); 66 list_add(config->modes, mode);
64 } 67 }
diff --git a/sway/config.c b/sway/config.c
index e4745a5c..8220ece0 100644
--- a/sway/config.c
+++ b/sway/config.c
@@ -82,6 +82,12 @@ static void free_mode(struct sway_mode *mode) {
82 } 82 }
83 list_free(mode->switch_bindings); 83 list_free(mode->switch_bindings);
84 } 84 }
85 if (mode->gesture_bindings) {
86 for (int i = 0; i < mode->gesture_bindings->length; i++) {
87 free_gesture_binding(mode->gesture_bindings->items[i]);
88 }
89 list_free(mode->gesture_bindings);
90 }
85 free(mode); 91 free(mode);
86} 92}
87 93
@@ -222,6 +228,7 @@ static void config_defaults(struct sway_config *config) {
222 if (!(config->current_mode->keycode_bindings = create_list())) goto cleanup; 228 if (!(config->current_mode->keycode_bindings = create_list())) goto cleanup;
223 if (!(config->current_mode->mouse_bindings = create_list())) goto cleanup; 229 if (!(config->current_mode->mouse_bindings = create_list())) goto cleanup;
224 if (!(config->current_mode->switch_bindings = create_list())) goto cleanup; 230 if (!(config->current_mode->switch_bindings = create_list())) goto cleanup;
231 if (!(config->current_mode->gesture_bindings = create_list())) goto cleanup;
225 list_add(config->modes, config->current_mode); 232 list_add(config->modes, config->current_mode);
226 233
227 config->floating_mod = 0; 234 config->floating_mod = 0;
diff --git a/sway/input/cursor.c b/sway/input/cursor.c
index 0b2f03a2..e87594ee 100644
--- a/sway/input/cursor.c
+++ b/sway/input/cursor.c
@@ -928,14 +928,28 @@ static void handle_request_pointer_set_cursor(struct wl_listener *listener,
928 event->hotspot_y, focused_client); 928 event->hotspot_y, focused_client);
929} 929}
930 930
931static void handle_pointer_hold_begin(struct wl_listener *listener, void *data) {
932 struct sway_cursor *cursor = wl_container_of(
933 listener, cursor, hold_begin);
934 struct wlr_pointer_hold_begin_event *event = data;
935 cursor_handle_activity_from_device(cursor, &event->pointer->base);
936 seatop_hold_begin(cursor->seat, event);
937}
938
939static void handle_pointer_hold_end(struct wl_listener *listener, void *data) {
940 struct sway_cursor *cursor = wl_container_of(
941 listener, cursor, hold_end);
942 struct wlr_pointer_hold_end_event *event = data;
943 cursor_handle_activity_from_device(cursor, &event->pointer->base);
944 seatop_hold_end(cursor->seat, event);
945}
946
931static void handle_pointer_pinch_begin(struct wl_listener *listener, void *data) { 947static void handle_pointer_pinch_begin(struct wl_listener *listener, void *data) {
932 struct sway_cursor *cursor = wl_container_of( 948 struct sway_cursor *cursor = wl_container_of(
933 listener, cursor, pinch_begin); 949 listener, cursor, pinch_begin);
934 struct wlr_pointer_pinch_begin_event *event = data; 950 struct wlr_pointer_pinch_begin_event *event = data;
935 cursor_handle_activity_from_device(cursor, &event->pointer->base); 951 cursor_handle_activity_from_device(cursor, &event->pointer->base);
936 wlr_pointer_gestures_v1_send_pinch_begin( 952 seatop_pinch_begin(cursor->seat, event);
937 cursor->pointer_gestures, cursor->seat->wlr_seat,
938 event->time_msec, event->fingers);
939} 953}
940 954
941static void handle_pointer_pinch_update(struct wl_listener *listener, void *data) { 955static void handle_pointer_pinch_update(struct wl_listener *listener, void *data) {
@@ -943,10 +957,7 @@ static void handle_pointer_pinch_update(struct wl_listener *listener, void *data
943 listener, cursor, pinch_update); 957 listener, cursor, pinch_update);
944 struct wlr_pointer_pinch_update_event *event = data; 958 struct wlr_pointer_pinch_update_event *event = data;
945 cursor_handle_activity_from_device(cursor, &event->pointer->base); 959 cursor_handle_activity_from_device(cursor, &event->pointer->base);
946 wlr_pointer_gestures_v1_send_pinch_update( 960 seatop_pinch_update(cursor->seat, event);
947 cursor->pointer_gestures, cursor->seat->wlr_seat,
948 event->time_msec, event->dx, event->dy,
949 event->scale, event->rotation);
950} 961}
951 962
952static void handle_pointer_pinch_end(struct wl_listener *listener, void *data) { 963static void handle_pointer_pinch_end(struct wl_listener *listener, void *data) {
@@ -954,9 +965,7 @@ static void handle_pointer_pinch_end(struct wl_listener *listener, void *data) {
954 listener, cursor, pinch_end); 965 listener, cursor, pinch_end);
955 struct wlr_pointer_pinch_end_event *event = data; 966 struct wlr_pointer_pinch_end_event *event = data;
956 cursor_handle_activity_from_device(cursor, &event->pointer->base); 967 cursor_handle_activity_from_device(cursor, &event->pointer->base);
957 wlr_pointer_gestures_v1_send_pinch_end( 968 seatop_pinch_end(cursor->seat, event);
958 cursor->pointer_gestures, cursor->seat->wlr_seat,
959 event->time_msec, event->cancelled);
960} 969}
961 970
962static void handle_pointer_swipe_begin(struct wl_listener *listener, void *data) { 971static void handle_pointer_swipe_begin(struct wl_listener *listener, void *data) {
@@ -964,9 +973,7 @@ static void handle_pointer_swipe_begin(struct wl_listener *listener, void *data)
964 listener, cursor, swipe_begin); 973 listener, cursor, swipe_begin);
965 struct wlr_pointer_swipe_begin_event *event = data; 974 struct wlr_pointer_swipe_begin_event *event = data;
966 cursor_handle_activity_from_device(cursor, &event->pointer->base); 975 cursor_handle_activity_from_device(cursor, &event->pointer->base);
967 wlr_pointer_gestures_v1_send_swipe_begin( 976 seatop_swipe_begin(cursor->seat, event);
968 cursor->pointer_gestures, cursor->seat->wlr_seat,
969 event->time_msec, event->fingers);
970} 977}
971 978
972static void handle_pointer_swipe_update(struct wl_listener *listener, void *data) { 979static void handle_pointer_swipe_update(struct wl_listener *listener, void *data) {
@@ -974,9 +981,7 @@ static void handle_pointer_swipe_update(struct wl_listener *listener, void *data
974 listener, cursor, swipe_update); 981 listener, cursor, swipe_update);
975 struct wlr_pointer_swipe_update_event *event = data; 982 struct wlr_pointer_swipe_update_event *event = data;
976 cursor_handle_activity_from_device(cursor, &event->pointer->base); 983 cursor_handle_activity_from_device(cursor, &event->pointer->base);
977 wlr_pointer_gestures_v1_send_swipe_update( 984 seatop_swipe_update(cursor->seat, event);
978 cursor->pointer_gestures, cursor->seat->wlr_seat,
979 event->time_msec, event->dx, event->dy);
980} 985}
981 986
982static void handle_pointer_swipe_end(struct wl_listener *listener, void *data) { 987static void handle_pointer_swipe_end(struct wl_listener *listener, void *data) {
@@ -984,29 +989,7 @@ static void handle_pointer_swipe_end(struct wl_listener *listener, void *data) {
984 listener, cursor, swipe_end); 989 listener, cursor, swipe_end);
985 struct wlr_pointer_swipe_end_event *event = data; 990 struct wlr_pointer_swipe_end_event *event = data;
986 cursor_handle_activity_from_device(cursor, &event->pointer->base); 991 cursor_handle_activity_from_device(cursor, &event->pointer->base);
987 wlr_pointer_gestures_v1_send_swipe_end( 992 seatop_swipe_end(cursor->seat, event);
988 cursor->pointer_gestures, cursor->seat->wlr_seat,
989 event->time_msec, event->cancelled);
990}
991
992static void handle_pointer_hold_begin(struct wl_listener *listener, void *data) {
993 struct sway_cursor *cursor = wl_container_of(
994 listener, cursor, hold_begin);
995 struct wlr_pointer_hold_begin_event *event = data;
996 cursor_handle_activity_from_device(cursor, &event->pointer->base);
997 wlr_pointer_gestures_v1_send_hold_begin(
998 cursor->pointer_gestures, cursor->seat->wlr_seat,
999 event->time_msec, event->fingers);
1000}
1001
1002static void handle_pointer_hold_end(struct wl_listener *listener, void *data) {
1003 struct sway_cursor *cursor = wl_container_of(
1004 listener, cursor, hold_end);
1005 struct wlr_pointer_hold_end_event *event = data;
1006 cursor_handle_activity_from_device(cursor, &event->pointer->base);
1007 wlr_pointer_gestures_v1_send_hold_end(
1008 cursor->pointer_gestures, cursor->seat->wlr_seat,
1009 event->time_msec, event->cancelled);
1010} 993}
1011 994
1012static void handle_image_surface_destroy(struct wl_listener *listener, 995static void handle_image_surface_destroy(struct wl_listener *listener,
@@ -1080,14 +1063,14 @@ void sway_cursor_destroy(struct sway_cursor *cursor) {
1080 wl_event_source_remove(cursor->hide_source); 1063 wl_event_source_remove(cursor->hide_source);
1081 1064
1082 wl_list_remove(&cursor->image_surface_destroy.link); 1065 wl_list_remove(&cursor->image_surface_destroy.link);
1066 wl_list_remove(&cursor->hold_begin.link);
1067 wl_list_remove(&cursor->hold_end.link);
1083 wl_list_remove(&cursor->pinch_begin.link); 1068 wl_list_remove(&cursor->pinch_begin.link);
1084 wl_list_remove(&cursor->pinch_update.link); 1069 wl_list_remove(&cursor->pinch_update.link);
1085 wl_list_remove(&cursor->pinch_end.link); 1070 wl_list_remove(&cursor->pinch_end.link);
1086 wl_list_remove(&cursor->swipe_begin.link); 1071 wl_list_remove(&cursor->swipe_begin.link);
1087 wl_list_remove(&cursor->swipe_update.link); 1072 wl_list_remove(&cursor->swipe_update.link);
1088 wl_list_remove(&cursor->swipe_end.link); 1073 wl_list_remove(&cursor->swipe_end.link);
1089 wl_list_remove(&cursor->hold_begin.link);
1090 wl_list_remove(&cursor->hold_end.link);
1091 wl_list_remove(&cursor->motion.link); 1074 wl_list_remove(&cursor->motion.link);
1092 wl_list_remove(&cursor->motion_absolute.link); 1075 wl_list_remove(&cursor->motion_absolute.link);
1093 wl_list_remove(&cursor->button.link); 1076 wl_list_remove(&cursor->button.link);
@@ -1131,23 +1114,27 @@ struct sway_cursor *sway_cursor_create(struct sway_seat *seat) {
1131 wl_list_init(&cursor->image_surface_destroy.link); 1114 wl_list_init(&cursor->image_surface_destroy.link);
1132 cursor->image_surface_destroy.notify = handle_image_surface_destroy; 1115 cursor->image_surface_destroy.notify = handle_image_surface_destroy;
1133 1116
1117 // gesture events
1134 cursor->pointer_gestures = wlr_pointer_gestures_v1_create(server.wl_display); 1118 cursor->pointer_gestures = wlr_pointer_gestures_v1_create(server.wl_display);
1135 cursor->pinch_begin.notify = handle_pointer_pinch_begin; 1119
1120 wl_signal_add(&wlr_cursor->events.hold_begin, &cursor->hold_begin);
1121 cursor->hold_begin.notify = handle_pointer_hold_begin;
1122 wl_signal_add(&wlr_cursor->events.hold_end, &cursor->hold_end);
1123 cursor->hold_end.notify = handle_pointer_hold_end;
1124
1136 wl_signal_add(&wlr_cursor->events.pinch_begin, &cursor->pinch_begin); 1125 wl_signal_add(&wlr_cursor->events.pinch_begin, &cursor->pinch_begin);
1137 cursor->pinch_update.notify = handle_pointer_pinch_update; 1126 cursor->pinch_begin.notify = handle_pointer_pinch_begin;
1138 wl_signal_add(&wlr_cursor->events.pinch_update, &cursor->pinch_update); 1127 wl_signal_add(&wlr_cursor->events.pinch_update, &cursor->pinch_update);
1139 cursor->pinch_end.notify = handle_pointer_pinch_end; 1128 cursor->pinch_update.notify = handle_pointer_pinch_update;
1140 wl_signal_add(&wlr_cursor->events.pinch_end, &cursor->pinch_end); 1129 wl_signal_add(&wlr_cursor->events.pinch_end, &cursor->pinch_end);
1141 cursor->swipe_begin.notify = handle_pointer_swipe_begin; 1130 cursor->pinch_end.notify = handle_pointer_pinch_end;
1131
1142 wl_signal_add(&wlr_cursor->events.swipe_begin, &cursor->swipe_begin); 1132 wl_signal_add(&wlr_cursor->events.swipe_begin, &cursor->swipe_begin);
1143 cursor->swipe_update.notify = handle_pointer_swipe_update; 1133 cursor->swipe_begin.notify = handle_pointer_swipe_begin;
1144 wl_signal_add(&wlr_cursor->events.swipe_update, &cursor->swipe_update); 1134 wl_signal_add(&wlr_cursor->events.swipe_update, &cursor->swipe_update);
1145 cursor->swipe_end.notify = handle_pointer_swipe_end; 1135 cursor->swipe_update.notify = handle_pointer_swipe_update;
1146 wl_signal_add(&wlr_cursor->events.swipe_end, &cursor->swipe_end); 1136 wl_signal_add(&wlr_cursor->events.swipe_end, &cursor->swipe_end);
1147 cursor->hold_begin.notify = handle_pointer_hold_begin; 1137 cursor->swipe_end.notify = handle_pointer_swipe_end;
1148 wl_signal_add(&wlr_cursor->events.hold_begin, &cursor->hold_begin);
1149 cursor->hold_end.notify = handle_pointer_hold_end;
1150 wl_signal_add(&wlr_cursor->events.hold_end, &cursor->hold_end);
1151 1138
1152 // input events 1139 // input events
1153 wl_signal_add(&wlr_cursor->events.motion, &cursor->motion); 1140 wl_signal_add(&wlr_cursor->events.motion, &cursor->motion);
diff --git a/sway/input/seat.c b/sway/input/seat.c
index 11c78154..fe61e0fe 100644
--- a/sway/input/seat.c
+++ b/sway/input/seat.c
@@ -1614,6 +1614,62 @@ void seatop_tablet_tool_motion(struct sway_seat *seat,
1614 } 1614 }
1615} 1615}
1616 1616
1617void seatop_hold_begin(struct sway_seat *seat,
1618 struct wlr_pointer_hold_begin_event *event) {
1619 if (seat->seatop_impl->hold_begin) {
1620 seat->seatop_impl->hold_begin(seat, event);
1621 }
1622}
1623
1624void seatop_hold_end(struct sway_seat *seat,
1625 struct wlr_pointer_hold_end_event *event) {
1626 if (seat->seatop_impl->hold_end) {
1627 seat->seatop_impl->hold_end(seat, event);
1628 }
1629}
1630
1631void seatop_pinch_begin(struct sway_seat *seat,
1632 struct wlr_pointer_pinch_begin_event *event) {
1633 if (seat->seatop_impl->pinch_begin) {
1634 seat->seatop_impl->pinch_begin(seat, event);
1635 }
1636}
1637
1638void seatop_pinch_update(struct sway_seat *seat,
1639 struct wlr_pointer_pinch_update_event *event) {
1640 if (seat->seatop_impl->pinch_update) {
1641 seat->seatop_impl->pinch_update(seat, event);
1642 }
1643}
1644
1645void seatop_pinch_end(struct sway_seat *seat,
1646 struct wlr_pointer_pinch_end_event *event) {
1647 if (seat->seatop_impl->pinch_end) {
1648 seat->seatop_impl->pinch_end(seat, event);
1649 }
1650}
1651
1652void seatop_swipe_begin(struct sway_seat *seat,
1653 struct wlr_pointer_swipe_begin_event *event) {
1654 if (seat->seatop_impl->swipe_begin) {
1655 seat->seatop_impl->swipe_begin(seat, event);
1656 }
1657}
1658
1659void seatop_swipe_update(struct sway_seat *seat,
1660 struct wlr_pointer_swipe_update_event *event) {
1661 if (seat->seatop_impl->swipe_update) {
1662 seat->seatop_impl->swipe_update(seat, event);
1663 }
1664}
1665
1666void seatop_swipe_end(struct sway_seat *seat,
1667 struct wlr_pointer_swipe_end_event *event) {
1668 if (seat->seatop_impl->swipe_end) {
1669 seat->seatop_impl->swipe_end(seat, event);
1670 }
1671}
1672
1617void seatop_rebase(struct sway_seat *seat, uint32_t time_msec) { 1673void seatop_rebase(struct sway_seat *seat, uint32_t time_msec) {
1618 if (seat->seatop_impl->rebase) { 1674 if (seat->seatop_impl->rebase) {
1619 seat->seatop_impl->rebase(seat, time_msec); 1675 seat->seatop_impl->rebase(seat, time_msec);
diff --git a/sway/input/seatop_default.c b/sway/input/seatop_default.c
index 15d1ca8b..2684e55a 100644
--- a/sway/input/seatop_default.c
+++ b/sway/input/seatop_default.c
@@ -4,6 +4,7 @@
4#include <wlr/types/wlr_cursor.h> 4#include <wlr/types/wlr_cursor.h>
5#include <wlr/types/wlr_tablet_v2.h> 5#include <wlr/types/wlr_tablet_v2.h>
6#include <wlr/types/wlr_xcursor_manager.h> 6#include <wlr/types/wlr_xcursor_manager.h>
7#include "gesture.h"
7#include "sway/desktop/transaction.h" 8#include "sway/desktop/transaction.h"
8#include "sway/input/cursor.h" 9#include "sway/input/cursor.h"
9#include "sway/input/seat.h" 10#include "sway/input/seat.h"
@@ -20,6 +21,7 @@ struct seatop_default_event {
20 struct sway_node *previous_node; 21 struct sway_node *previous_node;
21 uint32_t pressed_buttons[SWAY_CURSOR_PRESSED_BUTTONS_CAP]; 22 uint32_t pressed_buttons[SWAY_CURSOR_PRESSED_BUTTONS_CAP];
22 size_t pressed_button_count; 23 size_t pressed_button_count;
24 struct gesture_tracker gestures;
23}; 25};
24 26
25/*-----------------------------------------\ 27/*-----------------------------------------\
@@ -750,6 +752,304 @@ static void handle_pointer_axis(struct sway_seat *seat,
750 } 752 }
751} 753}
752 754
755/*------------------------------------\
756 * Functions used by gesture support /
757 *----------------------------------*/
758
759/**
760 * Check gesture binding for a specific gesture type and finger count.
761 * Returns true if binding is present, false otherwise
762 */
763static bool gesture_binding_check(list_t *bindings, enum gesture_type type,
764 uint8_t fingers, struct sway_input_device *device) {
765 char *input =
766 device ? input_device_get_identifier(device->wlr_device) : strdup("*");
767
768 for (int i = 0; i < bindings->length; ++i) {
769 struct sway_gesture_binding *binding = bindings->items[i];
770
771 // Check type and finger count
772 if (!gesture_check(&binding->gesture, type, fingers)) {
773 continue;
774 }
775
776 // Check that input matches
777 if (strcmp(binding->input, "*") != 0 &&
778 strcmp(binding->input, input) != 0) {
779 continue;
780 }
781
782 free(input);
783
784 return true;
785 }
786
787 free(input);
788
789 return false;
790}
791
792/**
793 * Return the gesture binding which matches gesture type, finger count
794 * and direction, otherwise return null.
795 */
796static struct sway_gesture_binding* gesture_binding_match(
797 list_t *bindings, struct gesture *gesture, const char *input) {
798 struct sway_gesture_binding *current = NULL;
799
800 // Find best matching binding
801 for (int i = 0; i < bindings->length; ++i) {
802 struct sway_gesture_binding *binding = bindings->items[i];
803 bool exact = binding->flags & BINDING_EXACT;
804
805 // Check gesture matching
806 if (!gesture_match(&binding->gesture, gesture, exact)) {
807 continue;
808 }
809
810 // Check input matching
811 if (strcmp(binding->input, "*") != 0 &&
812 strcmp(binding->input, input) != 0) {
813 continue;
814 }
815
816 // If we already have a match ...
817 if (current) {
818 // ... check if input matching is equivalent
819 if (strcmp(current->input, binding->input) == 0) {
820
821 // ... - do not override an exact binding
822 if (!exact && current->flags & BINDING_EXACT) {
823 continue;
824 }
825
826 // ... - and ensure direction matching is better or equal
827 if (gesture_compare(&current->gesture, &binding->gesture) > 0) {
828 continue;
829 }
830 } else if (strcmp(binding->input, "*") == 0) {
831 // ... do not accept worse input match
832 continue;
833 }
834 }
835
836 // Accept newer or better match
837 current = binding;
838
839 // If exact binding and input is found, quit search
840 if (strcmp(current->input, input) == 0 &&
841 gesture_compare(&current->gesture, gesture) == 0) {
842 break;
843 }
844 } // for all gesture bindings
845
846 return current;
847}
848
849// Wrapper around gesture_tracker_end to use tracker with sway bindings
850static struct sway_gesture_binding* gesture_tracker_end_and_match(
851 struct gesture_tracker *tracker, struct sway_input_device* device) {
852 // Determine name of input that received gesture
853 char *input = device
854 ? input_device_get_identifier(device->wlr_device)
855 : strdup("*");
856
857 // Match tracking result to binding
858 struct gesture *gesture = gesture_tracker_end(tracker);
859 struct sway_gesture_binding *binding = gesture_binding_match(
860 config->current_mode->gesture_bindings, gesture, input);
861 free(gesture);
862 free(input);
863
864 return binding;
865}
866
867// Small wrapper around seat_execute_command to work on gesture bindings
868static void gesture_binding_execute(struct sway_seat *seat,
869 struct sway_gesture_binding *binding) {
870 struct sway_binding *dummy_binding =
871 calloc(1, sizeof(struct sway_binding));
872 dummy_binding->type = BINDING_GESTURE;
873 dummy_binding->command = binding->command;
874
875 char *description = gesture_to_string(&binding->gesture);
876 sway_log(SWAY_DEBUG, "executing gesture binding: %s", description);
877 free(description);
878
879 seat_execute_command(seat, dummy_binding);
880
881 free(dummy_binding);
882}
883
884static void handle_hold_begin(struct sway_seat *seat,
885 struct wlr_pointer_hold_begin_event *event) {
886 // Start tracking gesture if there is a matching binding ...
887 struct sway_input_device *device =
888 event->pointer ? event->pointer->base.data : NULL;
889 list_t *bindings = config->current_mode->gesture_bindings;
890 if (gesture_binding_check(bindings, GESTURE_TYPE_HOLD, event->fingers, device)) {
891 struct seatop_default_event *seatop = seat->seatop_data;
892 gesture_tracker_begin(&seatop->gestures, GESTURE_TYPE_HOLD, event->fingers);
893 } else {
894 // ... otherwise forward to client
895 struct sway_cursor *cursor = seat->cursor;
896 wlr_pointer_gestures_v1_send_hold_begin(
897 cursor->pointer_gestures, cursor->seat->wlr_seat,
898 event->time_msec, event->fingers);
899 }
900}
901
902static void handle_hold_end(struct sway_seat *seat,
903 struct wlr_pointer_hold_end_event *event) {
904 // Ensure that gesture is being tracked and was not cancelled
905 struct seatop_default_event *seatop = seat->seatop_data;
906 if (!gesture_tracker_check(&seatop->gestures, GESTURE_TYPE_HOLD)) {
907 struct sway_cursor *cursor = seat->cursor;
908 wlr_pointer_gestures_v1_send_hold_end(
909 cursor->pointer_gestures, cursor->seat->wlr_seat,
910 event->time_msec, event->cancelled);
911 return;
912 }
913 if (event->cancelled) {
914 gesture_tracker_cancel(&seatop->gestures);
915 return;
916 }
917
918 // End gesture tracking and execute matched binding
919 struct sway_input_device *device =
920 event->pointer ? event->pointer->base.data : NULL;
921 struct sway_gesture_binding *binding = gesture_tracker_end_and_match(
922 &seatop->gestures, device);
923
924 if (binding) {
925 gesture_binding_execute(seat, binding);
926 }
927}
928
929static void handle_pinch_begin(struct sway_seat *seat,
930 struct wlr_pointer_pinch_begin_event *event) {
931 // Start tracking gesture if there is a matching binding ...
932 struct sway_input_device *device =
933 event->pointer ? event->pointer->base.data : NULL;
934 list_t *bindings = config->current_mode->gesture_bindings;
935 if (gesture_binding_check(bindings, GESTURE_TYPE_PINCH, event->fingers, device)) {
936 struct seatop_default_event *seatop = seat->seatop_data;
937 gesture_tracker_begin(&seatop->gestures, GESTURE_TYPE_PINCH, event->fingers);
938 } else {
939 // ... otherwise forward to client
940 struct sway_cursor *cursor = seat->cursor;
941 wlr_pointer_gestures_v1_send_pinch_begin(
942 cursor->pointer_gestures, cursor->seat->wlr_seat,
943 event->time_msec, event->fingers);
944 }
945}
946
947static void handle_pinch_update(struct sway_seat *seat,
948 struct wlr_pointer_pinch_update_event *event) {
949 // Update any ongoing tracking ...
950 struct seatop_default_event *seatop = seat->seatop_data;
951 if (gesture_tracker_check(&seatop->gestures, GESTURE_TYPE_PINCH)) {
952 gesture_tracker_update(&seatop->gestures, event->dx, event->dy,
953 event->scale, event->rotation);
954 } else {
955 // ... otherwise forward to client
956 struct sway_cursor *cursor = seat->cursor;
957 wlr_pointer_gestures_v1_send_pinch_update(
958 cursor->pointer_gestures,
959 cursor->seat->wlr_seat,
960 event->time_msec, event->dx, event->dy,
961 event->scale, event->rotation);
962 }
963}
964
965static void handle_pinch_end(struct sway_seat *seat,
966 struct wlr_pointer_pinch_end_event *event) {
967 // Ensure that gesture is being tracked and was not cancelled
968 struct seatop_default_event *seatop = seat->seatop_data;
969 if (!gesture_tracker_check(&seatop->gestures, GESTURE_TYPE_PINCH)) {
970 struct sway_cursor *cursor = seat->cursor;
971 wlr_pointer_gestures_v1_send_pinch_end(
972 cursor->pointer_gestures, cursor->seat->wlr_seat,
973 event->time_msec, event->cancelled);
974 return;
975 }
976 if (event->cancelled) {
977 gesture_tracker_cancel(&seatop->gestures);
978 return;
979 }
980
981 // End gesture tracking and execute matched binding
982 struct sway_input_device *device =
983 event->pointer ? event->pointer->base.data : NULL;
984 struct sway_gesture_binding *binding = gesture_tracker_end_and_match(
985 &seatop->gestures, device);
986
987 if (binding) {
988 gesture_binding_execute(seat, binding);
989 }
990}
991
992static void handle_swipe_begin(struct sway_seat *seat,
993 struct wlr_pointer_swipe_begin_event *event) {
994 // Start tracking gesture if there is a matching binding ...
995 struct sway_input_device *device =
996 event->pointer ? event->pointer->base.data : NULL;
997 list_t *bindings = config->current_mode->gesture_bindings;
998 if (gesture_binding_check(bindings, GESTURE_TYPE_SWIPE, event->fingers, device)) {
999 struct seatop_default_event *seatop = seat->seatop_data;
1000 gesture_tracker_begin(&seatop->gestures, GESTURE_TYPE_SWIPE, event->fingers);
1001 } else {
1002 // ... otherwise forward to client
1003 struct sway_cursor *cursor = seat->cursor;
1004 wlr_pointer_gestures_v1_send_swipe_begin(
1005 cursor->pointer_gestures, cursor->seat->wlr_seat,
1006 event->time_msec, event->fingers);
1007 }
1008}
1009
1010static void handle_swipe_update(struct sway_seat *seat,
1011 struct wlr_pointer_swipe_update_event *event) {
1012
1013 // Update any ongoing tracking ...
1014 struct seatop_default_event *seatop = seat->seatop_data;
1015 if (gesture_tracker_check(&seatop->gestures, GESTURE_TYPE_SWIPE)) {
1016 gesture_tracker_update(&seatop->gestures,
1017 event->dx, event->dy, NAN, NAN);
1018 } else {
1019 // ... otherwise forward to client
1020 struct sway_cursor *cursor = seat->cursor;
1021 wlr_pointer_gestures_v1_send_swipe_update(
1022 cursor->pointer_gestures, cursor->seat->wlr_seat,
1023 event->time_msec, event->dx, event->dy);
1024 }
1025}
1026
1027static void handle_swipe_end(struct sway_seat *seat,
1028 struct wlr_pointer_swipe_end_event *event) {
1029 // Ensure gesture is being tracked and was not cancelled
1030 struct seatop_default_event *seatop = seat->seatop_data;
1031 if (!gesture_tracker_check(&seatop->gestures, GESTURE_TYPE_SWIPE)) {
1032 struct sway_cursor *cursor = seat->cursor;
1033 wlr_pointer_gestures_v1_send_swipe_end(cursor->pointer_gestures,
1034 cursor->seat->wlr_seat, event->time_msec, event->cancelled);
1035 return;
1036 }
1037 if (event->cancelled) {
1038 gesture_tracker_cancel(&seatop->gestures);
1039 return;
1040 }
1041
1042 // End gesture tracking and execute matched binding
1043 struct sway_input_device *device =
1044 event->pointer ? event->pointer->base.data : NULL;
1045 struct sway_gesture_binding *binding = gesture_tracker_end_and_match(
1046 &seatop->gestures, device);
1047
1048 if (binding) {
1049 gesture_binding_execute(seat, binding);
1050 }
1051}
1052
753/*----------------------------------\ 1053/*----------------------------------\
754 * Functions used by handle_rebase / 1054 * Functions used by handle_rebase /
755 *--------------------------------*/ 1055 *--------------------------------*/
@@ -779,6 +1079,14 @@ static const struct sway_seatop_impl seatop_impl = {
779 .pointer_axis = handle_pointer_axis, 1079 .pointer_axis = handle_pointer_axis,
780 .tablet_tool_tip = handle_tablet_tool_tip, 1080 .tablet_tool_tip = handle_tablet_tool_tip,
781 .tablet_tool_motion = handle_tablet_tool_motion, 1081 .tablet_tool_motion = handle_tablet_tool_motion,
1082 .hold_begin = handle_hold_begin,
1083 .hold_end = handle_hold_end,
1084 .pinch_begin = handle_pinch_begin,
1085 .pinch_update = handle_pinch_update,
1086 .pinch_end = handle_pinch_end,
1087 .swipe_begin = handle_swipe_begin,
1088 .swipe_update = handle_swipe_update,
1089 .swipe_end = handle_swipe_end,
782 .rebase = handle_rebase, 1090 .rebase = handle_rebase,
783 .allow_set_cursor = true, 1091 .allow_set_cursor = true,
784}; 1092};
@@ -789,8 +1097,8 @@ void seatop_begin_default(struct sway_seat *seat) {
789 struct seatop_default_event *e = 1097 struct seatop_default_event *e =
790 calloc(1, sizeof(struct seatop_default_event)); 1098 calloc(1, sizeof(struct seatop_default_event));
791 sway_assert(e, "Unable to allocate seatop_default_event"); 1099 sway_assert(e, "Unable to allocate seatop_default_event");
1100
792 seat->seatop_impl = &seatop_impl; 1101 seat->seatop_impl = &seatop_impl;
793 seat->seatop_data = e; 1102 seat->seatop_data = e;
794
795 seatop_rebase(seat, 0); 1103 seatop_rebase(seat, 0);
796} 1104}
diff --git a/sway/meson.build b/sway/meson.build
index 6762ef2d..5dd9cad2 100644
--- a/sway/meson.build
+++ b/sway/meson.build
@@ -67,6 +67,7 @@ sway_sources = files(
67 'commands/force_focus_wrapping.c', 67 'commands/force_focus_wrapping.c',
68 'commands/fullscreen.c', 68 'commands/fullscreen.c',
69 'commands/gaps.c', 69 'commands/gaps.c',
70 'commands/gesture.c',
70 'commands/hide_edge_borders.c', 71 'commands/hide_edge_borders.c',
71 'commands/inhibit_idle.c', 72 'commands/inhibit_idle.c',
72 'commands/kill.c', 73 'commands/kill.c',
diff --git a/sway/sway.5.scd b/sway/sway.5.scd
index 2780370f..db7886f0 100644
--- a/sway/sway.5.scd
+++ b/sway/sway.5.scd
@@ -488,6 +488,62 @@ runtime.
488 bindswitch lid:toggle exec echo "Lid moved" 488 bindswitch lid:toggle exec echo "Lid moved"
489``` 489```
490 490
491*bindgesture* [--exact] [--input-device=<device>] [--no-warn] \
492<gesture>[:<fingers>][:directions] <command>
493 Binds _gesture_ to execute the sway command _command_ when detected.
494 Currently supports the _hold_, _pinch_ or _swipe_ gesture. Optionally
495 can be limited to bind to a certain number of _fingers_ or, for a
496 _pinch_ or _swipe_ gesture, to certain _directions_.
497
498[[ *type*
499:[ *fingers*
500:< *direction*
501| hold
502:- 1 - 5
503: none
504| swipe
505: 3 - 5
506: up, down, left, right
507| pinch
508: 2 - 5
509: all above + inward, outward, clockwise, counterclockwise
510
511 The _fingers_ can be limited to any sensible number or left empty to accept
512 any finger counts.
513 Valid directions are _up_, _down_, _left_ and _right_, as well as _inward_,
514 _outward_, _clockwise_, _counterclockwise_ for the _pinch_ gesture.
515 Multiple directions can be combined by a plus.
516
517 If a _input-device_ is given, the binding will only be executed for
518 that input device and will be executed instead of any binding that is
519 generic to all devices. By default, if you overwrite a binding,
520 swaynag will give you a warning. To silence this, use the _--no-warn_ flag.
521
522 The _--exact_ flag can be used to ensure a binding only matches when exactly
523 all specified directions are matched and nothing more. If there is matching
524 binding with _--exact_, it will be preferred.
525
526 The priority for matching bindings is as follows: input device, then
527 exact matches followed by matches with the highest number of matching
528 directions.
529
530 Gestures executed while the pointer is above a bar are not handled by sway.
531 See the respective documentation, e.g. *bindgesture* in *sway-bar*(5).
532
533 Example:
534```
535 # Allow switching between workspaces with left and right swipes
536 bindgesture swipe:right workspace prev
537 bindgesture swipe:left workspace next
538
539 # Allow container movements by pinching them
540 bindgesture pinch:inward+up move up
541 bindgesture pinch:inward+down move down
542 bindgesture pinch:inward+left move left
543 bindgesture pinch:inward+right move right
544
545```
546
491*client.background* <color> 547*client.background* <color>
492 This command is ignored and is only present for i3 compatibility. 548 This command is ignored and is only present for i3 compatibility.
493 549
@@ -792,6 +848,11 @@ The default colors are:
792*unbindswitch* <switch>:<state> 848*unbindswitch* <switch>:<state>
793 Removes a binding for when <switch> changes to <state>. 849 Removes a binding for when <switch> changes to <state>.
794 850
851*unbindgesture* [--exact] [--input-device=<device>] \
852<gesture>[:<fingers>][:directions]
853 Removes a binding for the specified _gesture_, _fingers_
854 and _directions_ combination.
855
795*unbindsym* [--whole-window] [--border] [--exclude-titlebar] [--release] [--locked] \ 856*unbindsym* [--whole-window] [--border] [--exclude-titlebar] [--release] [--locked] \
796[--to-code] [--input-device=<device>] <key combo> 857[--to-code] [--input-device=<device>] <key combo>
797 Removes the binding for _key combo_ that was previously bound with the 858 Removes the binding for _key combo_ that was previously bound with the