aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Franzen <Florian.Franzen@gmail.com>2022-04-23 10:27:47 +0200
committerSimon Ser <contact@emersion.fr>2022-05-30 12:20:43 +0200
commitcab2189aa64d04ba79dc2cbf19400435b47cdbd2 (patch)
tree450ac51fbc75c73ed1dc6728bc05b08366ace785
parenta535ed310f756a57129683c9d81c6a65e8338977 (diff)
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 @@
+#define _POSIX_C_SOURCE 200809L
+#include "gesture.h"
+
+#include <math.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "list.h"
+#include "log.h"
+#include "stringop.h"
+
+const uint8_t GESTURE_FINGERS_ANY = 0;
+
+// Helper to easily allocate and format string
+static char *strformat(const char *format, ...) {
+ va_list args;
+ va_start(args, format);
+ int length = vsnprintf(NULL, 0, format, args) + 1;
+ va_end(args);
+
+ char *result = malloc(length);
+ if (result) {
+ va_start(args, format);
+ vsnprintf(result, length, format, args);
+ va_end(args);
+ }
+
+ return result;
+}
+
+char *gesture_parse(const char *input, struct gesture *output) {
+ // Clear output in case of failure
+ output->type = GESTURE_TYPE_NONE;
+ output->fingers = GESTURE_FINGERS_ANY;
+ output->directions = GESTURE_DIRECTION_NONE;
+
+ // Split input type, fingers and directions
+ list_t *split = split_string(input, ":");
+ if (split->length < 1 || split->length > 3) {
+ return strformat(
+ "expected <gesture>[:<fingers>][:direction], got %s",
+ input);
+ }
+
+ // Parse gesture type
+ if (strcmp(split->items[0], "hold") == 0) {
+ output->type = GESTURE_TYPE_HOLD;
+ } else if (strcmp(split->items[0], "pinch") == 0) {
+ output->type = GESTURE_TYPE_PINCH;
+ } else if (strcmp(split->items[0], "swipe") == 0) {
+ output->type = GESTURE_TYPE_SWIPE;
+ } else {
+ return strformat("expected hold|pinch|swipe, got %s",
+ split->items[0]);
+ }
+
+ // Parse optional arguments
+ if (split->length > 1) {
+ char *next = split->items[1];
+
+ // Try to parse as finger count (1-9)
+ if (strlen(next) == 1 && '1' <= next[0] && next[0] <= '9') {
+ output->fingers = atoi(next);
+
+ // Move to next if available
+ next = split->length == 3 ? split->items[2] : NULL;
+ } else if (split->length == 3) {
+ // Fail here if argument can only be finger count
+ return strformat("expected 1-9, got %s", next);
+ }
+
+ // If there is an argument left, try to parse as direction
+ if (next) {
+ list_t *directions = split_string(next, "+");
+
+ for (int i = 0; i < directions->length; ++i) {
+ const char *item = directions->items[i];
+ if (strcmp(item, "any") == 0) {
+ continue;
+ } else if (strcmp(item, "up") == 0) {
+ output->directions |= GESTURE_DIRECTION_UP;
+ } else if (strcmp(item, "down") == 0) {
+ output->directions |= GESTURE_DIRECTION_DOWN;
+ } else if (strcmp(item, "left") == 0) {
+ output->directions |= GESTURE_DIRECTION_LEFT;
+ } else if (strcmp(item, "right") == 0) {
+ output->directions |= GESTURE_DIRECTION_RIGHT;
+ } else if (strcmp(item, "inward") == 0) {
+ output->directions |= GESTURE_DIRECTION_INWARD;
+ } else if (strcmp(item, "outward") == 0) {
+ output->directions |= GESTURE_DIRECTION_OUTWARD;
+ } else if (strcmp(item, "clockwise") == 0) {
+ output->directions |= GESTURE_DIRECTION_CLOCKWISE;
+ } else if (strcmp(item, "counterclockwise") == 0) {
+ output->directions |= GESTURE_DIRECTION_COUNTERCLOCKWISE;
+ } else {
+ return strformat("expected direction, got %s", item);
+ }
+ }
+ list_free_items_and_destroy(directions);
+ }
+ } // if optional args
+
+ list_free_items_and_destroy(split);
+
+ return NULL;
+}
+
+const char *gesture_type_string(enum gesture_type type) {
+ switch (type) {
+ case GESTURE_TYPE_NONE:
+ return "none";
+ case GESTURE_TYPE_HOLD:
+ return "hold";
+ case GESTURE_TYPE_PINCH:
+ return "pinch";
+ case GESTURE_TYPE_SWIPE:
+ return "swipe";
+ }
+
+ return NULL;
+}
+
+const char *gesture_direction_string(enum gesture_direction direction) {
+ switch (direction) {
+ case GESTURE_DIRECTION_NONE:
+ return "none";
+ case GESTURE_DIRECTION_UP:
+ return "up";
+ case GESTURE_DIRECTION_DOWN:
+ return "down";
+ case GESTURE_DIRECTION_LEFT:
+ return "left";
+ case GESTURE_DIRECTION_RIGHT:
+ return "right";
+ case GESTURE_DIRECTION_INWARD:
+ return "inward";
+ case GESTURE_DIRECTION_OUTWARD:
+ return "outward";
+ case GESTURE_DIRECTION_CLOCKWISE:
+ return "clockwise";
+ case GESTURE_DIRECTION_COUNTERCLOCKWISE:
+ return "counterclockwise";
+ }
+
+ return NULL;
+}
+
+// Helper to turn combination of directions flags into string representation.
+static char *gesture_directions_to_string(uint32_t directions) {
+ char *result = NULL;
+
+ for (uint8_t bit = 0; bit < 32; bit++) {
+ uint32_t masked = directions & (1 << bit);
+ if (masked) {
+ const char *name = gesture_direction_string(masked);
+
+ if (!name) {
+ name = "unknown";
+ }
+
+ if (!result) {
+ result = strdup(name);
+ } else {
+ char *new = strformat("%s+%s", result, name);
+ free(result);
+ result = new;
+ }
+ }
+ }
+
+ if(!result) {
+ return strdup("any");
+ }
+
+ return result;
+}
+
+char *gesture_to_string(struct gesture *gesture) {
+ char *directions = gesture_directions_to_string(gesture->directions);
+ char *result = strformat("%s:%u:%s",
+ gesture_type_string(gesture->type),
+ gesture->fingers, directions);
+ free(directions);
+ return result;
+}
+
+bool gesture_check(struct gesture *target, enum gesture_type type, uint8_t fingers) {
+ // Check that gesture type matches
+ if (target->type != type) {
+ return false;
+ }
+
+ // Check that finger count matches
+ if (target->fingers != GESTURE_FINGERS_ANY && target->fingers != fingers) {
+ return false;
+ }
+
+ return true;
+}
+
+bool gesture_match(struct gesture *target, struct gesture *to_match, bool exact) {
+ // Check type and fingers
+ if (!gesture_check(target, to_match->type, to_match->fingers)) {
+ return false;
+ }
+
+ // Enforce exact matches ...
+ if (exact && target->directions != to_match->directions) {
+ return false;
+ }
+
+ // ... or ensure all target directions are matched
+ return (target->directions & to_match->directions) == target->directions;
+}
+
+bool gesture_equal(struct gesture *a, struct gesture *b) {
+ return a->type == b->type
+ && a->fingers == b->fingers
+ && a->directions == b->directions;
+}
+
+// Return count of set bits in directions bit field.
+static uint8_t gesture_directions_count(uint32_t directions) {
+ uint8_t count = 0;
+ for (; directions; directions >>= 1) {
+ count += directions & 1;
+ }
+ return count;
+}
+
+// Compare direction bit count of two direction.
+static int8_t gesture_directions_compare(uint32_t a, uint32_t b) {
+ return gesture_directions_count(a) - gesture_directions_count(b);
+}
+
+// Compare two direction based on their direction bit count
+int8_t gesture_compare(struct gesture *a, struct gesture *b) {
+ return gesture_directions_compare(a->directions, b->directions);
+}
+
+void gesture_tracker_begin(struct gesture_tracker *tracker,
+ enum gesture_type type, uint8_t fingers) {
+ tracker->type = type;
+ tracker->fingers = fingers;
+
+ tracker->dx = 0.0;
+ tracker->dy = 0.0;
+ tracker->scale = 1.0;
+ tracker->rotation = 0.0;
+
+ sway_log(SWAY_DEBUG, "begin tracking %s:%u:? gesture",
+ gesture_type_string(type), fingers);
+}
+
+bool gesture_tracker_check(struct gesture_tracker *tracker, enum gesture_type type) {
+ return tracker->type == type;
+}
+
+void gesture_tracker_update(struct gesture_tracker *tracker,
+ double dx, double dy, double scale, double rotation) {
+ if (tracker->type == GESTURE_TYPE_HOLD) {
+ sway_assert(false, "hold does not update.");
+ return;
+ }
+
+ tracker->dx += dx;
+ tracker->dy += dy;
+
+ if (tracker->type == GESTURE_TYPE_PINCH) {
+ tracker->scale = scale;
+ tracker->rotation += rotation;
+ }
+
+ sway_log(SWAY_DEBUG, "update tracking %s:%u:? gesture: %f %f %f %f",
+ gesture_type_string(tracker->type),
+ tracker->fingers,
+ tracker->dx, tracker->dy,
+ tracker->scale, tracker->rotation);
+}
+
+void gesture_tracker_cancel(struct gesture_tracker *tracker) {
+ sway_log(SWAY_DEBUG, "cancel tracking %s:%u:? gesture",
+ gesture_type_string(tracker->type), tracker->fingers);
+
+ tracker->type = GESTURE_TYPE_NONE;
+}
+
+struct gesture *gesture_tracker_end(struct gesture_tracker *tracker) {
+ struct gesture *result = calloc(1, sizeof(struct gesture));
+
+ // Ignore gesture under some threshold
+ // TODO: Make configurable
+ const double min_rotation = 5;
+ const double min_scale_delta = 0.1;
+
+ // Determine direction
+ switch(tracker->type) {
+ // Gestures with scale and rotation
+ case GESTURE_TYPE_PINCH:
+ if (tracker->rotation > min_rotation) {
+ result->directions |= GESTURE_DIRECTION_CLOCKWISE;
+ }
+ if (tracker->rotation < -min_rotation) {
+ result->directions |= GESTURE_DIRECTION_COUNTERCLOCKWISE;
+ }
+
+ if (tracker->scale > (1.0 + min_scale_delta)) {
+ result->directions |= GESTURE_DIRECTION_OUTWARD;
+ }
+ if (tracker->scale < (1.0 - min_scale_delta)) {
+ result->directions |= GESTURE_DIRECTION_INWARD;
+ }
+ __attribute__ ((fallthrough));
+ // Gestures with dx and dy
+ case GESTURE_TYPE_SWIPE:
+ if (fabs(tracker->dx) > fabs(tracker->dy)) {
+ if (tracker->dx > 0) {
+ result->directions |= GESTURE_DIRECTION_RIGHT;
+ } else {
+ result->directions |= GESTURE_DIRECTION_LEFT;
+ }
+ } else {
+ if (tracker->dy > 0) {
+ result->directions |= GESTURE_DIRECTION_DOWN;
+ } else {
+ result->directions |= GESTURE_DIRECTION_UP;
+ }
+ }
+ // Gesture without any direction
+ case GESTURE_TYPE_HOLD:
+ break;
+ // Not tracking any gesture
+ case GESTURE_TYPE_NONE:
+ sway_assert(false, "Not tracking any gesture.");
+ return result;
+ }
+
+ result->type = tracker->type;
+ result->fingers = tracker->fingers;
+
+ char *description = gesture_to_string(result);
+ sway_log(SWAY_DEBUG, "end tracking gesture: %s", description);
+ free(description);
+
+ tracker->type = GESTURE_TYPE_NONE;
+
+ return result;
+}
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(
files(
'background-image.c',
'cairo.c',
+ 'gesture.c',
'ipc-client.c',
'log.c',
'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 @@
+#ifndef _SWAY_GESTURE_H
+#define _SWAY_GESTURE_H
+
+#include <stdbool.h>
+#include <stdint.h>
+
+/**
+ * A gesture type used in binding.
+ */
+enum gesture_type {
+ GESTURE_TYPE_NONE = 0,
+ GESTURE_TYPE_HOLD,
+ GESTURE_TYPE_PINCH,
+ GESTURE_TYPE_SWIPE,
+};
+
+// Turns single type enum value to constant string representation.
+const char *gesture_type_string(enum gesture_type direction);
+
+// Value to use to accept any finger count
+extern const uint8_t GESTURE_FINGERS_ANY;
+
+/**
+ * A gesture direction used in binding.
+ */
+enum gesture_direction {
+ GESTURE_DIRECTION_NONE = 0,
+ // Directions based on delta x and y
+ GESTURE_DIRECTION_UP = 1 << 0,
+ GESTURE_DIRECTION_DOWN = 1 << 1,
+ GESTURE_DIRECTION_LEFT = 1 << 2,
+ GESTURE_DIRECTION_RIGHT = 1 << 3,
+ // Directions based on scale
+ GESTURE_DIRECTION_INWARD = 1 << 4,
+ GESTURE_DIRECTION_OUTWARD = 1 << 5,
+ // Directions based on rotation
+ GESTURE_DIRECTION_CLOCKWISE = 1 << 6,
+ GESTURE_DIRECTION_COUNTERCLOCKWISE = 1 << 7,
+};
+
+// Turns single direction enum value to constant string representation.
+const char *gesture_direction_string(enum gesture_direction direction);
+
+/**
+ * Struct representing a pointer gesture
+ */
+struct gesture {
+ enum gesture_type type;
+ uint8_t fingers;
+ uint32_t directions;
+};
+
+/**
+ * Parses gesture from <gesture>[:<fingers>][:<directions>] string.
+ *
+ * Return NULL on success, otherwise error message string
+ */
+char *gesture_parse(const char *input, struct gesture *output);
+
+// Turns gesture into string representation
+char *gesture_to_string(struct gesture *gesture);
+
+// Check if gesture is of certain type and finger count.
+bool gesture_check(struct gesture *target,
+ enum gesture_type type, uint8_t fingers);
+
+// Check if a gesture target/binding is match by other gesture/input
+bool gesture_match(struct gesture *target,
+ struct gesture *to_match, bool exact);
+
+// Returns true if gesture are exactly the same
+bool gesture_equal(struct gesture *a, struct gesture *b);
+
+// Compare distance between two matched target gestures.
+int8_t gesture_compare(struct gesture *a, struct gesture *b);
+
+// Small helper struct to track gestures over time
+struct gesture_tracker {
+ enum gesture_type type;
+ uint8_t fingers;
+ double dx, dy;
+ double scale;
+ double rotation;
+};
+
+// Begin gesture tracking
+void gesture_tracker_begin(struct gesture_tracker *tracker,
+ enum gesture_type type, uint8_t fingers);
+
+// Check if the provides type is currently being tracked
+bool gesture_tracker_check(struct gesture_tracker *tracker,
+ enum gesture_type type);
+
+// Update gesture track with new data point
+void gesture_tracker_update(struct gesture_tracker *tracker, double dx,
+ double dy, double scale, double rotation);
+
+// Reset tracker
+void gesture_tracker_cancel(struct gesture_tracker *tracker);
+
+// Reset tracker and return gesture tracked
+struct gesture *gesture_tracker_end(struct gesture_tracker *tracker);
+
+#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 @@
#define _SWAY_STRINGOP_H
#include <stdbool.h>
+#include <stddef.h>
#include "list.h"
void 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;
sway_cmd cmd_assign;
sway_cmd cmd_bar;
sway_cmd cmd_bindcode;
+sway_cmd cmd_bindgesture;
sway_cmd cmd_bindswitch;
sway_cmd cmd_bindsym;
sway_cmd cmd_border;
@@ -191,6 +192,7 @@ sway_cmd cmd_titlebar_border_thickness;
sway_cmd cmd_titlebar_padding;
sway_cmd cmd_unbindcode;
sway_cmd cmd_unbindswitch;
+sway_cmd cmd_unbindgesture;
sway_cmd cmd_unbindsym;
sway_cmd cmd_unmark;
sway_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 @@
#include <xkbcommon/xkbcommon.h>
#include <xf86drmMode.h>
#include "../include/config.h"
+#include "gesture.h"
#include "list.h"
#include "swaynag.h"
#include "tree/container.h"
@@ -32,7 +33,8 @@ enum binding_input_type {
BINDING_KEYSYM,
BINDING_MOUSECODE,
BINDING_MOUSESYM,
- BINDING_SWITCH
+ BINDING_SWITCH, // dummy, only used to call seat_execute_command
+ BINDING_GESTURE // dummy, only used to call seat_execute_command
};
enum binding_flags {
@@ -45,6 +47,7 @@ enum binding_flags {
BINDING_RELOAD = 1 << 6, // switch only; (re)trigger binding on reload
BINDING_INHIBITED = 1 << 7, // keyboard only: ignore shortcut inhibitor
BINDING_NOREPEAT = 1 << 8, // keyboard only; do not trigger when repeating a held key
+ BINDING_EXACT = 1 << 9, // gesture only; only trigger on exact match
};
/**
@@ -79,6 +82,16 @@ struct sway_switch_binding {
};
/**
+ * A gesture binding and an associated command.
+ */
+struct sway_gesture_binding {
+ char *input;
+ uint32_t flags;
+ struct gesture gesture;
+ char *command;
+};
+
+/**
* Focus on window activation.
*/
enum sway_fowa {
@@ -97,6 +110,7 @@ struct sway_mode {
list_t *keycode_bindings;
list_t *mouse_bindings;
list_t *switch_bindings;
+ list_t *gesture_bindings;
bool pango;
};
@@ -689,6 +703,8 @@ void free_sway_binding(struct sway_binding *sb);
void free_switch_binding(struct sway_switch_binding *binding);
+void free_gesture_binding(struct sway_gesture_binding *binding);
+
void seat_execute_command(struct sway_seat *seat, struct sway_binding *binding);
void 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 {
bool active_confine_requires_warp;
struct wlr_pointer_gestures_v1 *pointer_gestures;
+ struct wl_listener hold_begin;
+ struct wl_listener hold_end;
struct wl_listener pinch_begin;
struct wl_listener pinch_update;
struct wl_listener pinch_end;
struct wl_listener swipe_begin;
struct wl_listener swipe_update;
struct wl_listener swipe_end;
- struct wl_listener hold_begin;
- struct wl_listener hold_end;
struct wl_listener motion;
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 {
void (*pointer_motion)(struct sway_seat *seat, uint32_t time_msec);
void (*pointer_axis)(struct sway_seat *seat,
struct wlr_pointer_axis_event *event);
+ void (*hold_begin)(struct sway_seat *seat,
+ struct wlr_pointer_hold_begin_event *event);
+ void (*hold_end)(struct sway_seat *seat,
+ struct wlr_pointer_hold_end_event *event);
+ void (*pinch_begin)(struct sway_seat *seat,
+ struct wlr_pointer_pinch_begin_event *event);
+ void (*pinch_update)(struct sway_seat *seat,
+ struct wlr_pointer_pinch_update_event *event);
+ void (*pinch_end)(struct sway_seat *seat,
+ struct wlr_pointer_pinch_end_event *event);
+ void (*swipe_begin)(struct sway_seat *seat,
+ struct wlr_pointer_swipe_begin_event *event);
+ void (*swipe_update)(struct sway_seat *seat,
+ struct wlr_pointer_swipe_update_event *event);
+ void (*swipe_end)(struct sway_seat *seat,
+ struct wlr_pointer_swipe_end_event *event);
void (*rebase)(struct sway_seat *seat, uint32_t time_msec);
void (*tablet_tool_motion)(struct sway_seat *seat,
struct sway_tablet_tool *tool, uint32_t time_msec);
@@ -287,6 +303,25 @@ void seatop_tablet_tool_tip(struct sway_seat *seat,
void seatop_tablet_tool_motion(struct sway_seat *seat,
struct sway_tablet_tool *tool, uint32_t time_msec);
+void seatop_hold_begin(struct sway_seat *seat,
+ struct wlr_pointer_hold_begin_event *event);
+void seatop_hold_end(struct sway_seat *seat,
+ struct wlr_pointer_hold_end_event *event);
+
+void seatop_pinch_begin(struct sway_seat *seat,
+ struct wlr_pointer_pinch_begin_event *event);
+void seatop_pinch_update(struct sway_seat *seat,
+ struct wlr_pointer_pinch_update_event *event);
+void seatop_pinch_end(struct sway_seat *seat,
+ struct wlr_pointer_pinch_end_event *event);
+
+void seatop_swipe_begin(struct sway_seat *seat,
+ struct wlr_pointer_swipe_begin_event *event);
+void seatop_swipe_update(struct sway_seat *seat,
+ struct wlr_pointer_swipe_update_event *event);
+void seatop_swipe_end(struct sway_seat *seat,
+ struct wlr_pointer_swipe_end_event *event);
+
void seatop_rebase(struct sway_seat *seat, uint32_t time_msec);
/**
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[] = {
{ "assign", cmd_assign },
{ "bar", cmd_bar },
{ "bindcode", cmd_bindcode },
+ { "bindgesture", cmd_bindgesture },
{ "bindswitch", cmd_bindswitch },
{ "bindsym", cmd_bindsym },
{ "client.background", cmd_client_noop },
@@ -92,6 +93,7 @@ static const struct cmd_handler handlers[] = {
{ "titlebar_border_thickness", cmd_titlebar_border_thickness },
{ "titlebar_padding", cmd_titlebar_padding },
{ "unbindcode", cmd_unbindcode },
+ { "unbindgesture", cmd_unbindgesture },
{ "unbindswitch", cmd_unbindswitch },
{ "unbindsym", cmd_unbindsym },
{ "workspace", cmd_workspace },
@@ -407,6 +409,7 @@ struct cmd_results *config_command(char *exec, char **new_block) {
&& handler->handle != cmd_bindsym
&& handler->handle != cmd_bindcode
&& handler->handle != cmd_bindswitch
+ && handler->handle != cmd_bindgesture
&& handler->handle != cmd_set
&& handler->handle != cmd_for_window
&& (*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 @@
+#define _POSIX_C_SOURCE 200809L
+#include "sway/config.h"
+
+#include "gesture.h"
+#include "log.h"
+#include "stringop.h"
+#include "sway/commands.h"
+
+void free_gesture_binding(struct sway_gesture_binding *binding) {
+ if (!binding) {
+ return;
+ }
+ free(binding->input);
+ free(binding->command);
+ free(binding);
+}
+
+/**
+ * Returns true if the bindings have the same gesture type, direction, etc
+ */
+static bool binding_gesture_equal(struct sway_gesture_binding *binding_a,
+ struct sway_gesture_binding *binding_b) {
+ if (strcmp(binding_a->input, binding_b->input) != 0) {
+ return false;
+ }
+
+ if (!gesture_equal(&binding_a->gesture, &binding_b->gesture)) {
+ return false;
+ }
+
+ if ((binding_a->flags & BINDING_EXACT) !=
+ (binding_b->flags & BINDING_EXACT)) {
+ return false;
+ }
+ return true;
+}
+
+/**
+ * Add gesture binding to config
+ */
+static struct cmd_results *gesture_binding_add(
+ struct sway_gesture_binding *binding,
+ const char *gesturecombo, bool warn) {
+ list_t *mode_bindings = config->current_mode->gesture_bindings;
+ // overwrite the binding if it already exists
+ bool overwritten = false;
+ for (int i = 0; i < mode_bindings->length; ++i) {
+ struct sway_gesture_binding *config_binding = mode_bindings->items[i];
+ if (binding_gesture_equal(binding, config_binding)) {
+ sway_log(SWAY_INFO, "Overwriting binding '%s' to `%s` from `%s`",
+ gesturecombo, binding->command, config_binding->command);
+ if (warn) {
+ config_add_swaynag_warning("Overwriting binding"
+ "'%s' to `%s` from `%s`",
+ gesturecombo, binding->command,
+ config_binding->command);
+ }
+ free_gesture_binding(config_binding);
+ mode_bindings->items[i] = binding;
+ overwritten = true;
+ }
+ }
+
+ if (!overwritten) {
+ list_add(mode_bindings, binding);
+ sway_log(SWAY_DEBUG, "bindgesture - Bound %s to command `%s`",
+ gesturecombo, binding->command);
+ }
+
+ return cmd_results_new(CMD_SUCCESS, NULL);
+}
+
+/**
+ * Remove gesture binding from config
+ */
+static struct cmd_results *gesture_binding_remove(
+ struct sway_gesture_binding *binding, const char *gesturecombo) {
+ list_t *mode_bindings = config->current_mode->gesture_bindings;
+ for (int i = 0; i < mode_bindings->length; ++i) {
+ struct sway_gesture_binding *config_binding = mode_bindings->items[i];
+ if (binding_gesture_equal(binding, config_binding)) {
+ free_gesture_binding(config_binding);
+ free_gesture_binding(binding);
+ list_del(mode_bindings, i);
+ sway_log(SWAY_DEBUG, "unbindgesture - Unbound %s gesture",
+ gesturecombo);
+ return cmd_results_new(CMD_SUCCESS, NULL);
+ }
+ }
+
+ free_gesture_binding(binding);
+ return cmd_results_new(CMD_FAILURE, "Could not find gesture binding `%s`",
+ gesturecombo);
+}
+
+/**
+ * Parse and execute bindgesture or unbindgesture command.
+ */
+static struct cmd_results *cmd_bind_or_unbind_gesture(int argc, char **argv, bool unbind) {
+ int minargs = 2;
+ char *bindtype = "bindgesture";
+ if (unbind) {
+ minargs--;
+ bindtype = "unbindgesture";
+ }
+
+ struct cmd_results *error = NULL;
+ if ((error = checkarg(argc, bindtype, EXPECTED_AT_LEAST, minargs))) {
+ return error;
+ }
+ struct sway_gesture_binding *binding = calloc(1, sizeof(struct sway_gesture_binding));
+ if (!binding) {
+ return cmd_results_new(CMD_FAILURE, "Unable to allocate binding");
+ }
+ binding->input = strdup("*");
+
+ bool warn = true;
+
+ // Handle flags
+ while (argc > 0) {
+ if (strcmp("--exact", argv[0]) == 0) {
+ binding->flags |= BINDING_EXACT;
+ } else if (strcmp("--no-warn", argv[0]) == 0) {
+ warn = false;
+ } else if (strncmp("--input-device=", argv[0],
+ strlen("--input-device=")) == 0) {
+ free(binding->input);
+ binding->input = strdup(argv[0] + strlen("--input-device="));
+ } else {
+ break;
+ }
+ argv++;
+ argc--;
+ }
+
+ if (argc < minargs) {
+ free(binding);
+ return cmd_results_new(CMD_FAILURE,
+ "Invalid %s command (expected at least %d "
+ "non-option arguments, got %d)", bindtype, minargs, argc);
+ }
+
+ char* errmsg = NULL;
+ if ((errmsg = gesture_parse(argv[0], &binding->gesture))) {
+ free(binding);
+ struct cmd_results *final = cmd_results_new(CMD_FAILURE,
+ "Invalid %s command (%s)",
+ bindtype, errmsg);
+ free(errmsg);
+ return final;
+ }
+
+ if (unbind) {
+ return gesture_binding_remove(binding, argv[0]);
+ }
+ binding->command = join_args(argv + 1, argc - 1);
+ return gesture_binding_add(binding, argv[0], warn);
+}
+
+struct cmd_results *cmd_bindgesture(int argc, char **argv) {
+ return cmd_bind_or_unbind_gesture(argc, argv, false);
+}
+
+struct cmd_results *cmd_unbindgesture(int argc, char **argv) {
+ return cmd_bind_or_unbind_gesture(argc, argv, true);
+}
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 @@
// Must be in order for the bsearch
static const struct cmd_handler mode_handlers[] = {
{ "bindcode", cmd_bindcode },
+ { "bindgesture", cmd_bindgesture },
{ "bindswitch", cmd_bindswitch },
{ "bindsym", cmd_bindsym },
{ "set", cmd_set },
{ "unbindcode", cmd_unbindcode },
+ { "unbindgesture", cmd_unbindgesture },
{ "unbindswitch", cmd_unbindswitch },
{ "unbindsym", cmd_unbindsym },
};
@@ -59,6 +61,7 @@ struct cmd_results *cmd_mode(int argc, char **argv) {
mode->keycode_bindings = create_list();
mode->mouse_bindings = create_list();
mode->switch_bindings = create_list();
+ mode->gesture_bindings = create_list();
mode->pango = pango;
list_add(config->modes, mode);
}
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) {
}
list_free(mode->switch_bindings);
}
+ if (mode->gesture_bindings) {
+ for (int i = 0; i < mode->gesture_bindings->length; i++) {
+ free_gesture_binding(mode->gesture_bindings->items[i]);
+ }
+ list_free(mode->gesture_bindings);
+ }
free(mode);
}
@@ -222,6 +228,7 @@ static void config_defaults(struct sway_config *config) {
if (!(config->current_mode->keycode_bindings = create_list())) goto cleanup;
if (!(config->current_mode->mouse_bindings = create_list())) goto cleanup;
if (!(config->current_mode->switch_bindings = create_list())) goto cleanup;
+ if (!(config->current_mode->gesture_bindings = create_list())) goto cleanup;
list_add(config->modes, config->current_mode);
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,
event->hotspot_y, focused_client);
}
+static void handle_pointer_hold_begin(struct wl_listener *listener, void *data) {
+ struct sway_cursor *cursor = wl_container_of(
+ listener, cursor, hold_begin);
+ struct wlr_pointer_hold_begin_event *event = data;
+ cursor_handle_activity_from_device(cursor, &event->pointer->base);
+ seatop_hold_begin(cursor->seat, event);
+}
+
+static void handle_pointer_hold_end(struct wl_listener *listener, void *data) {
+ struct sway_cursor *cursor = wl_container_of(
+ listener, cursor, hold_end);
+ struct wlr_pointer_hold_end_event *event = data;
+ cursor_handle_activity_from_device(cursor, &event->pointer->base);
+ seatop_hold_end(cursor->seat, event);
+}
+
static void handle_pointer_pinch_begin(struct wl_listener *listener, void *data) {
struct sway_cursor *cursor = wl_container_of(
listener, cursor, pinch_begin);
struct wlr_pointer_pinch_begin_event *event = data;
cursor_handle_activity_from_device(cursor, &event->pointer->base);
- wlr_pointer_gestures_v1_send_pinch_begin(
- cursor->pointer_gestures, cursor->seat->wlr_seat,
- event->time_msec, event->fingers);
+ seatop_pinch_begin(cursor->seat, event);
}
static 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
listener, cursor, pinch_update);
struct wlr_pointer_pinch_update_event *event = data;
cursor_handle_activity_from_device(cursor, &event->pointer->base);
- wlr_pointer_gestures_v1_send_pinch_update(
- cursor->pointer_gestures, cursor->seat->wlr_seat,
- event->time_msec, event->dx, event->dy,
- event->scale, event->rotation);
+ seatop_pinch_update(cursor->seat, event);
}
static 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) {
listener, cursor, pinch_end);
struct wlr_pointer_pinch_end_event *event = data;
cursor_handle_activity_from_device(cursor, &event->pointer->base);
- wlr_pointer_gestures_v1_send_pinch_end(
- cursor->pointer_gestures, cursor->seat->wlr_seat,
- event->time_msec, event->cancelled);
+ seatop_pinch_end(cursor->seat, event);
}
static 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)
listener, cursor, swipe_begin);
struct wlr_pointer_swipe_begin_event *event = data;
cursor_handle_activity_from_device(cursor, &event->pointer->base);
- wlr_pointer_gestures_v1_send_swipe_begin(
- cursor->pointer_gestures, cursor->seat->wlr_seat,
- event->time_msec, event->fingers);
+ seatop_swipe_begin(cursor->seat, event);
}
static 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
listener, cursor, swipe_update);
struct wlr_pointer_swipe_update_event *event = data;
cursor_handle_activity_from_device(cursor, &event->pointer->base);
- wlr_pointer_gestures_v1_send_swipe_update(
- cursor->pointer_gestures, cursor->seat->wlr_seat,
- event->time_msec, event->dx, event->dy);
+ seatop_swipe_update(cursor->seat, event);
}
static 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) {
listener, cursor, swipe_end);
struct wlr_pointer_swipe_end_event *event = data;
cursor_handle_activity_from_device(cursor, &event->pointer->base);
- wlr_pointer_gestures_v1_send_swipe_end(
- cursor->pointer_gestures, cursor->seat->wlr_seat,
- event->time_msec, event->cancelled);
-}
-
-static void handle_pointer_hold_begin(struct wl_listener *listener, void *data) {
- struct sway_cursor *cursor = wl_container_of(
- listener, cursor, hold_begin);
- struct wlr_pointer_hold_begin_event *event = data;
- cursor_handle_activity_from_device(cursor, &event->pointer->base);
- wlr_pointer_gestures_v1_send_hold_begin(
- cursor->pointer_gestures, cursor->seat->wlr_seat,
- event->time_msec, event->fingers);
-}
-
-static void handle_pointer_hold_end(struct wl_listener *listener, void *data) {
- struct sway_cursor *cursor = wl_container_of(
- listener, cursor, hold_end);
- struct wlr_pointer_hold_end_event *event = data;
- cursor_handle_activity_from_device(cursor, &event->pointer->base);
- wlr_pointer_gestures_v1_send_hold_end(
- cursor->pointer_gestures, cursor->seat->wlr_seat,
- event->time_msec, event->cancelled);
+ seatop_swipe_end(cursor->seat, event);
}
static void handle_image_surface_destroy(struct wl_listener *listener,
@@ -1080,14 +1063,14 @@ void sway_cursor_destroy(struct sway_cursor *cursor) {
wl_event_source_remove(cursor->hide_source);
wl_list_remove(&cursor->image_surface_destroy.link);
+ wl_list_remove(&cursor->hold_begin.link);
+ wl_list_remove(&cursor->hold_end.link);
wl_list_remove(&cursor->pinch_begin.link);
wl_list_remove(&cursor->pinch_update.link);
wl_list_remove(&cursor->pinch_end.link);
wl_list_remove(&cursor->swipe_begin.link);
wl_list_remove(&cursor->swipe_update.link);
wl_list_remove(&cursor->swipe_end.link);
- wl_list_remove(&cursor->hold_begin.link);
- wl_list_remove(&cursor->hold_end.link);
wl_list_remove(&cursor->motion.link);
wl_list_remove(&cursor->motion_absolute.link);
wl_list_remove(&cursor->button.link);
@@ -1131,23 +1114,27 @@ struct sway_cursor *sway_cursor_create(struct sway_seat *seat) {
wl_list_init(&cursor->image_surface_destroy.link);
cursor->image_surface_destroy.notify = handle_image_surface_destroy;
+ // gesture events
cursor->pointer_gestures = wlr_pointer_gestures_v1_create(server.wl_display);
- cursor->pinch_begin.notify = handle_pointer_pinch_begin;
+
+ wl_signal_add(&wlr_cursor->events.hold_begin, &cursor->hold_begin);
+ cursor->hold_begin.notify = handle_pointer_hold_begin;
+ wl_signal_add(&wlr_cursor->events.hold_end, &cursor->hold_end);
+ cursor->hold_end.notify = handle_pointer_hold_end;
+
wl_signal_add(&wlr_cursor->events.pinch_begin, &cursor->pinch_begin);
- cursor->pinch_update.notify = handle_pointer_pinch_update;
+ cursor->pinch_begin.notify = handle_pointer_pinch_begin;
wl_signal_add(&wlr_cursor->events.pinch_update, &cursor->pinch_update);
- cursor->pinch_end.notify = handle_pointer_pinch_end;
+ cursor->pinch_update.notify = handle_pointer_pinch_update;
wl_signal_add(&wlr_cursor->events.pinch_end, &cursor->pinch_end);
- cursor->swipe_begin.notify = handle_pointer_swipe_begin;
+ cursor->pinch_end.notify = handle_pointer_pinch_end;
+
wl_signal_add(&wlr_cursor->events.swipe_begin, &cursor->swipe_begin);
- cursor->swipe_update.notify = handle_pointer_swipe_update;
+ cursor->swipe_begin.notify = handle_pointer_swipe_begin;
wl_signal_add(&wlr_cursor->events.swipe_update, &cursor->swipe_update);
- cursor->swipe_end.notify = handle_pointer_swipe_end;
+ cursor->swipe_update.notify = handle_pointer_swipe_update;
wl_signal_add(&wlr_cursor->events.swipe_end, &cursor->swipe_end);
- cursor->hold_begin.notify = handle_pointer_hold_begin;
- wl_signal_add(&wlr_cursor->events.hold_begin, &cursor->hold_begin);
- cursor->hold_end.notify = handle_pointer_hold_end;
- wl_signal_add(&wlr_cursor->events.hold_end, &cursor->hold_end);
+ cursor->swipe_end.notify = handle_pointer_swipe_end;
// input events
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,
}
}
+void seatop_hold_begin(struct sway_seat *seat,
+ struct wlr_pointer_hold_begin_event *event) {
+ if (seat->seatop_impl->hold_begin) {
+ seat->seatop_impl->hold_begin(seat, event);
+ }
+}
+
+void seatop_hold_end(struct sway_seat *seat,
+ struct wlr_pointer_hold_end_event *event) {
+ if (seat->seatop_impl->hold_end) {
+ seat->seatop_impl->hold_end(seat, event);
+ }
+}
+
+void seatop_pinch_begin(struct sway_seat *seat,
+ struct wlr_pointer_pinch_begin_event *event) {
+ if (seat->seatop_impl->pinch_begin) {
+ seat->seatop_impl->pinch_begin(seat, event);
+ }
+}
+
+void seatop_pinch_update(struct sway_seat *seat,
+ struct wlr_pointer_pinch_update_event *event) {
+ if (seat->seatop_impl->pinch_update) {
+ seat->seatop_impl->pinch_update(seat, event);
+ }
+}
+
+void seatop_pinch_end(struct sway_seat *seat,
+ struct wlr_pointer_pinch_end_event *event) {
+ if (seat->seatop_impl->pinch_end) {
+ seat->seatop_impl->pinch_end(seat, event);
+ }
+}
+
+void seatop_swipe_begin(struct sway_seat *seat,
+ struct wlr_pointer_swipe_begin_event *event) {
+ if (seat->seatop_impl->swipe_begin) {
+ seat->seatop_impl->swipe_begin(seat, event);
+ }
+}
+
+void seatop_swipe_update(struct sway_seat *seat,
+ struct wlr_pointer_swipe_update_event *event) {
+ if (seat->seatop_impl->swipe_update) {
+ seat->seatop_impl->swipe_update(seat, event);
+ }
+}
+
+void seatop_swipe_end(struct sway_seat *seat,
+ struct wlr_pointer_swipe_end_event *event) {
+ if (seat->seatop_impl->swipe_end) {
+ seat->seatop_impl->swipe_end(seat, event);
+ }
+}
+
void seatop_rebase(struct sway_seat *seat, uint32_t time_msec) {
if (seat->seatop_impl->rebase) {
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 @@
#include <wlr/types/wlr_cursor.h>
#include <wlr/types/wlr_tablet_v2.h>
#include <wlr/types/wlr_xcursor_manager.h>
+#include "gesture.h"
#include "sway/desktop/transaction.h"
#include "sway/input/cursor.h"
#include "sway/input/seat.h"
@@ -20,6 +21,7 @@ struct seatop_default_event {
struct sway_node *previous_node;
uint32_t pressed_buttons[SWAY_CURSOR_PRESSED_BUTTONS_CAP];
size_t pressed_button_count;
+ struct gesture_tracker gestures;
};
/*-----------------------------------------\
@@ -750,6 +752,304 @@ static void handle_pointer_axis(struct sway_seat *seat,
}
}
+/*------------------------------------\
+ * Functions used by gesture support /
+ *----------------------------------*/
+
+/**
+ * Check gesture binding for a specific gesture type and finger count.
+ * Returns true if binding is present, false otherwise
+ */
+static bool gesture_binding_check(list_t *bindings, enum gesture_type type,
+ uint8_t fingers, struct sway_input_device *device) {
+ char *input =
+ device ? input_device_get_identifier(device->wlr_device) : strdup("*");
+
+ for (int i = 0; i < bindings->length; ++i) {
+ struct sway_gesture_binding *binding = bindings->items[i];
+
+ // Check type and finger count
+ if (!gesture_check(&binding->gesture, type, fingers)) {
+ continue;
+ }
+
+ // Check that input matches
+ if (strcmp(binding->input, "*") != 0 &&
+ strcmp(binding->input, input) != 0) {
+ continue;
+ }
+
+ free(input);
+
+ return true;
+ }
+
+ free(input);
+
+ return false;
+}
+
+/**
+ * Return the gesture binding which matches gesture type, finger count
+ * and direction, otherwise return null.
+ */
+static struct sway_gesture_binding* gesture_binding_match(
+ list_t *bindings, struct gesture *gesture, const char *input) {
+ struct sway_gesture_binding *current = NULL;
+
+ // Find best matching binding
+ for (int i = 0; i < bindings->length; ++i) {
+ struct sway_gesture_binding *binding = bindings->items[i];
+ bool exact = binding->flags & BINDING_EXACT;
+
+ // Check gesture matching
+ if (!gesture_match(&binding->gesture, gesture, exact)) {
+ continue;
+ }
+
+ // Check input matching
+ if (strcmp(binding->input, "*") != 0 &&
+ strcmp(binding->input, input) != 0) {
+ continue;
+ }
+
+ // If we already have a match ...
+ if (current) {
+ // ... check if input matching is equivalent
+ if (strcmp(current->input, binding->input) == 0) {
+
+ // ... - do not override an exact binding
+ if (!exact && current->flags & BINDING_EXACT) {
+ continue;
+ }
+
+ // ... - and ensure direction matching is better or equal
+ if (gesture_compare(&current->gesture, &binding->gesture) > 0) {
+ continue;
+ }
+ } else if (strcmp(binding->input, "*") == 0) {
+ // ... do not accept worse input match
+ continue;
+ }
+ }
+
+ // Accept newer or better match
+ current = binding;
+
+ // If exact binding and input is found, quit search
+ if (strcmp(current->input, input) == 0 &&
+ gesture_compare(&current->gesture, gesture) == 0) {
+ break;
+ }
+ } // for all gesture bindings
+
+ return current;
+}
+
+// Wrapper around gesture_tracker_end to use tracker with sway bindings
+static struct sway_gesture_binding* gesture_tracker_end_and_match(
+ struct gesture_tracker *tracker, struct sway_input_device* device) {
+ // Determine name of input that received gesture
+ char *input = device
+ ? input_device_get_identifier(device->wlr_device)
+ : strdup("*");
+
+ // Match tracking result to binding
+ struct gesture *gesture = gesture_tracker_end(tracker);
+ struct sway_gesture_binding *binding = gesture_binding_match(
+ config->current_mode->gesture_bindings, gesture, input);
+ free(gesture);
+ free(input);
+
+ return binding;
+}
+
+// Small wrapper around seat_execute_command to work on gesture bindings
+static void gesture_binding_execute(struct sway_seat *seat,
+ struct sway_gesture_binding *binding) {
+ struct sway_binding *dummy_binding =
+ calloc(1, sizeof(struct sway_binding));
+ dummy_binding->type = BINDING_GESTURE;
+ dummy_binding->command = binding->command;
+
+ char *description = gesture_to_string(&binding->gesture);
+ sway_log(SWAY_DEBUG, "executing gesture binding: %s", description);
+ free(description);
+
+ seat_execute_command(seat, dummy_binding);
+
+ free(dummy_binding);
+}
+
+static void handle_hold_begin(struct sway_seat *seat,
+ struct wlr_pointer_hold_begin_event *event) {
+ // Start tracking gesture if there is a matching binding ...
+ struct sway_input_device *device =
+ event->pointer ? event->pointer->base.data : NULL;
+ list_t *bindings = config->current_mode->gesture_bindings;
+ if (gesture_binding_check(bindings, GESTURE_TYPE_HOLD, event->fingers, device)) {
+ struct seatop_default_event *seatop = seat->seatop_data;
+ gesture_tracker_begin(&seatop->gestures, GESTURE_TYPE_HOLD, event->fingers);
+ } else {
+ // ... otherwise forward to client
+ struct sway_cursor *cursor = seat->cursor;
+ wlr_pointer_gestures_v1_send_hold_begin(
+ cursor->pointer_gestures, cursor->seat->wlr_seat,
+ event->time_msec, event->fingers);
+ }
+}
+
+static void handle_hold_end(struct sway_seat *seat,
+ struct wlr_pointer_hold_end_event *event) {
+ // Ensure that gesture is being tracked and was not cancelled
+ struct seatop_default_event *seatop = seat->seatop_data;
+ if (!gesture_tracker_check(&seatop->gestures, GESTURE_TYPE_HOLD)) {
+ struct sway_cursor *cursor = seat->cursor;
+ wlr_pointer_gestures_v1_send_hold_end(
+ cursor->pointer_gestures, cursor->seat->wlr_seat,
+ event->time_msec, event->cancelled);
+ return;
+ }
+ if (event->cancelled) {
+ gesture_tracker_cancel(&seatop->gestures);
+ return;
+ }
+
+ // End gesture tracking and execute matched binding
+ struct sway_input_device *device =
+ event->pointer ? event->pointer->base.data : NULL;
+ struct sway_gesture_binding *binding = gesture_tracker_end_and_match(
+ &seatop->gestures, device);
+
+ if (binding) {
+ gesture_binding_execute(seat, binding);
+ }
+}
+
+static void handle_pinch_begin(struct sway_seat *seat,
+ struct wlr_pointer_pinch_begin_event *event) {
+ // Start tracking gesture if there is a matching binding ...
+ struct sway_input_device *device =
+ event->pointer ? event->pointer->base.data : NULL;
+ list_t *bindings = config->current_mode->gesture_bindings;
+ if (gesture_binding_check(bindings, GESTURE_TYPE_PINCH, event->fingers, device)) {
+ struct seatop_default_event *seatop = seat->seatop_data;
+ gesture_tracker_begin(&seatop->gestures, GESTURE_TYPE_PINCH, event->fingers);
+ } else {
+ // ... otherwise forward to client
+ struct sway_cursor *cursor = seat->cursor;
+ wlr_pointer_gestures_v1_send_pinch_begin(
+ cursor->pointer_gestures, cursor->seat->wlr_seat,
+ event->time_msec, event->fingers);
+ }
+}
+
+static void handle_pinch_update(struct sway_seat *seat,
+ struct wlr_pointer_pinch_update_event *event) {
+ // Update any ongoing tracking ...
+ struct seatop_default_event *seatop = seat->seatop_data;
+ if (gesture_tracker_check(&seatop->gestures, GESTURE_TYPE_PINCH)) {
+ gesture_tracker_update(&seatop->gestures, event->dx, event->dy,
+ event->scale, event->rotation);
+ } else {
+ // ... otherwise forward to client
+ struct sway_cursor *cursor = seat->cursor;
+ wlr_pointer_gestures_v1_send_pinch_update(
+ cursor->pointer_gestures,
+ cursor->seat->wlr_seat,
+ event->time_msec, event->dx, event->dy,
+ event->scale, event->rotation);
+ }
+}
+
+static void handle_pinch_end(struct sway_seat *seat,
+ struct wlr_pointer_pinch_end_event *event) {
+ // Ensure that gesture is being tracked and was not cancelled
+ struct seatop_default_event *seatop = seat->seatop_data;
+ if (!gesture_tracker_check(&seatop->gestures, GESTURE_TYPE_PINCH)) {
+ struct sway_cursor *cursor = seat->cursor;
+ wlr_pointer_gestures_v1_send_pinch_end(
+ cursor->pointer_gestures, cursor->seat->wlr_seat,
+ event->time_msec, event->cancelled);
+ return;
+ }
+ if (event->cancelled) {
+ gesture_tracker_cancel(&seatop->gestures);
+ return;
+ }
+
+ // End gesture tracking and execute matched binding
+ struct sway_input_device *device =
+ event->pointer ? event->pointer->base.data : NULL;
+ struct sway_gesture_binding *binding = gesture_tracker_end_and_match(
+ &seatop->gestures, device);
+
+ if (binding) {
+ gesture_binding_execute(seat, binding);
+ }
+}
+
+static void handle_swipe_begin(struct sway_seat *seat,
+ struct wlr_pointer_swipe_begin_event *event) {
+ // Start tracking gesture if there is a matching binding ...
+ struct sway_input_device *device =
+ event->pointer ? event->pointer->base.data : NULL;
+ list_t *bindings = config->current_mode->gesture_bindings;
+ if (gesture_binding_check(bindings, GESTURE_TYPE_SWIPE, event->fingers, device)) {
+ struct seatop_default_event *seatop = seat->seatop_data;
+ gesture_tracker_begin(&seatop->gestures, GESTURE_TYPE_SWIPE, event->fingers);
+ } else {
+ // ... otherwise forward to client
+ struct sway_cursor *cursor = seat->cursor;
+ wlr_pointer_gestures_v1_send_swipe_begin(
+ cursor->pointer_gestures, cursor->seat->wlr_seat,
+ event->time_msec, event->fingers);
+ }
+}
+
+static void handle_swipe_update(struct sway_seat *seat,
+ struct wlr_pointer_swipe_update_event *event) {
+
+ // Update any ongoing tracking ...
+ struct seatop_default_event *seatop = seat->seatop_data;
+ if (gesture_tracker_check(&seatop->gestures, GESTURE_TYPE_SWIPE)) {
+ gesture_tracker_update(&seatop->gestures,
+ event->dx, event->dy, NAN, NAN);
+ } else {
+ // ... otherwise forward to client
+ struct sway_cursor *cursor = seat->cursor;
+ wlr_pointer_gestures_v1_send_swipe_update(
+ cursor->pointer_gestures, cursor->seat->wlr_seat,
+ event->time_msec, event->dx, event->dy);
+ }
+}
+
+static void handle_swipe_end(struct sway_seat *seat,
+ struct wlr_pointer_swipe_end_event *event) {
+ // Ensure gesture is being tracked and was not cancelled
+ struct seatop_default_event *seatop = seat->seatop_data;
+ if (!gesture_tracker_check(&seatop->gestures, GESTURE_TYPE_SWIPE)) {
+ struct sway_cursor *cursor = seat->cursor;
+ wlr_pointer_gestures_v1_send_swipe_end(cursor->pointer_gestures,
+ cursor->seat->wlr_seat, event->time_msec, event->cancelled);
+ return;
+ }
+ if (event->cancelled) {
+ gesture_tracker_cancel(&seatop->gestures);
+ return;
+ }
+
+ // End gesture tracking and execute matched binding
+ struct sway_input_device *device =
+ event->pointer ? event->pointer->base.data : NULL;
+ struct sway_gesture_binding *binding = gesture_tracker_end_and_match(
+ &seatop->gestures, device);
+
+ if (binding) {
+ gesture_binding_execute(seat, binding);
+ }
+}
+
/*----------------------------------\
* Functions used by handle_rebase /
*--------------------------------*/
@@ -779,6 +1079,14 @@ static const struct sway_seatop_impl seatop_impl = {
.pointer_axis = handle_pointer_axis,
.tablet_tool_tip = handle_tablet_tool_tip,
.tablet_tool_motion = handle_tablet_tool_motion,
+ .hold_begin = handle_hold_begin,
+ .hold_end = handle_hold_end,
+ .pinch_begin = handle_pinch_begin,
+ .pinch_update = handle_pinch_update,
+ .pinch_end = handle_pinch_end,
+ .swipe_begin = handle_swipe_begin,
+ .swipe_update = handle_swipe_update,
+ .swipe_end = handle_swipe_end,
.rebase = handle_rebase,
.allow_set_cursor = true,
};
@@ -789,8 +1097,8 @@ void seatop_begin_default(struct sway_seat *seat) {
struct seatop_default_event *e =
calloc(1, sizeof(struct seatop_default_event));
sway_assert(e, "Unable to allocate seatop_default_event");
+
seat->seatop_impl = &seatop_impl;
seat->seatop_data = e;
-
seatop_rebase(seat, 0);
}
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(
'commands/force_focus_wrapping.c',
'commands/fullscreen.c',
'commands/gaps.c',
+ 'commands/gesture.c',
'commands/hide_edge_borders.c',
'commands/inhibit_idle.c',
'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.
bindswitch lid:toggle exec echo "Lid moved"
```
+*bindgesture* [--exact] [--input-device=<device>] [--no-warn] \
+<gesture>[:<fingers>][:directions] <command>
+ Binds _gesture_ to execute the sway command _command_ when detected.
+ Currently supports the _hold_, _pinch_ or _swipe_ gesture. Optionally
+ can be limited to bind to a certain number of _fingers_ or, for a
+ _pinch_ or _swipe_ gesture, to certain _directions_.
+
+[[ *type*
+:[ *fingers*
+:< *direction*
+| hold
+:- 1 - 5
+: none
+| swipe
+: 3 - 5
+: up, down, left, right
+| pinch
+: 2 - 5
+: all above + inward, outward, clockwise, counterclockwise
+
+ The _fingers_ can be limited to any sensible number or left empty to accept
+ any finger counts.
+ Valid directions are _up_, _down_, _left_ and _right_, as well as _inward_,
+ _outward_, _clockwise_, _counterclockwise_ for the _pinch_ gesture.
+ Multiple directions can be combined by a plus.
+
+ If a _input-device_ is given, the binding will only be executed for
+ that input device and will be executed instead of any binding that is
+ generic to all devices. By default, if you overwrite a binding,
+ swaynag will give you a warning. To silence this, use the _--no-warn_ flag.
+
+ The _--exact_ flag can be used to ensure a binding only matches when exactly
+ all specified directions are matched and nothing more. If there is matching
+ binding with _--exact_, it will be preferred.
+
+ The priority for matching bindings is as follows: input device, then
+ exact matches followed by matches with the highest number of matching
+ directions.
+
+ Gestures executed while the pointer is above a bar are not handled by sway.
+ See the respective documentation, e.g. *bindgesture* in *sway-bar*(5).
+
+ Example:
+```
+ # Allow switching between workspaces with left and right swipes
+ bindgesture swipe:right workspace prev
+ bindgesture swipe:left workspace next
+
+ # Allow container movements by pinching them
+ bindgesture pinch:inward+up move up
+ bindgesture pinch:inward+down move down
+ bindgesture pinch:inward+left move left
+ bindgesture pinch:inward+right move right
+
+```
+
*client.background* <color>
This command is ignored and is only present for i3 compatibility.
@@ -792,6 +848,11 @@ The default colors are:
*unbindswitch* <switch>:<state>
Removes a binding for when <switch> changes to <state>.
+*unbindgesture* [--exact] [--input-device=<device>] \
+<gesture>[:<fingers>][:directions]
+ Removes a binding for the specified _gesture_, _fingers_
+ and _directions_ combination.
+
*unbindsym* [--whole-window] [--border] [--exclude-titlebar] [--release] [--locked] \
[--to-code] [--input-device=<device>] <key combo>
Removes the binding for _key combo_ that was previously bound with the