From cab2189aa64d04ba79dc2cbf19400435b47cdbd2 Mon Sep 17 00:00:00 2001 From: Florian Franzen Date: Sat, 23 Apr 2022 10:27:47 +0200 Subject: [PATCH] sway: add bindgesture command Co-authored-by: Michael Weiser --- common/gesture.c | 350 ++++++++++++++++++++++++++++++++++++ common/meson.build | 1 + include/gesture.h | 104 +++++++++++ include/stringop.h | 1 + include/sway/commands.h | 2 + include/sway/config.h | 18 +- include/sway/input/cursor.h | 4 +- include/sway/input/seat.h | 35 ++++ sway/commands.c | 3 + sway/commands/gesture.c | 166 +++++++++++++++++ sway/commands/mode.c | 3 + sway/config.c | 7 + sway/input/cursor.c | 153 ++++++++-------- sway/input/seat.c | 56 ++++++ sway/input/seatop_default.c | 310 +++++++++++++++++++++++++++++++- sway/meson.build | 1 + sway/sway.5.scd | 61 +++++++ 17 files changed, 1188 insertions(+), 87 deletions(-) create mode 100644 common/gesture.c create mode 100644 include/gesture.h create mode 100644 sway/commands/gesture.c 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 +#include +#include +#include +#include +#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 [:][: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 +#include + +/** + * 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 [:][:] 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 +#include #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 #include #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 }; /** @@ -78,6 +81,16 @@ struct sway_switch_binding { char *command; }; +/** + * 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. */ @@ -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,75 +928,12 @@ static void handle_request_pointer_set_cursor(struct wl_listener *listener, event->hotspot_y, focused_client); } -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); -} - -static void handle_pointer_pinch_update(struct wl_listener *listener, void *data) { - struct sway_cursor *cursor = wl_container_of( - 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); -} - -static void handle_pointer_pinch_end(struct wl_listener *listener, void *data) { - struct sway_cursor *cursor = wl_container_of( - 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); -} - -static void handle_pointer_swipe_begin(struct wl_listener *listener, void *data) { - struct sway_cursor *cursor = wl_container_of( - 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); -} - -static void handle_pointer_swipe_update(struct wl_listener *listener, void *data) { - struct sway_cursor *cursor = wl_container_of( - 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); -} - -static void handle_pointer_swipe_end(struct wl_listener *listener, void *data) { - struct sway_cursor *cursor = wl_container_of( - 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); + seatop_hold_begin(cursor->seat, event); } static void handle_pointer_hold_end(struct wl_listener *listener, void *data) { @@ -1004,9 +941,55 @@ static void handle_pointer_hold_end(struct wl_listener *listener, void *data) { 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_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); + seatop_pinch_begin(cursor->seat, event); +} + +static void handle_pointer_pinch_update(struct wl_listener *listener, void *data) { + struct sway_cursor *cursor = wl_container_of( + listener, cursor, pinch_update); + struct wlr_pointer_pinch_update_event *event = data; + cursor_handle_activity_from_device(cursor, &event->pointer->base); + seatop_pinch_update(cursor->seat, event); +} + +static void handle_pointer_pinch_end(struct wl_listener *listener, void *data) { + struct sway_cursor *cursor = wl_container_of( + listener, cursor, pinch_end); + struct wlr_pointer_pinch_end_event *event = data; + cursor_handle_activity_from_device(cursor, &event->pointer->base); + seatop_pinch_end(cursor->seat, event); +} + +static void handle_pointer_swipe_begin(struct wl_listener *listener, void *data) { + struct sway_cursor *cursor = wl_container_of( + listener, cursor, swipe_begin); + struct wlr_pointer_swipe_begin_event *event = data; + cursor_handle_activity_from_device(cursor, &event->pointer->base); + seatop_swipe_begin(cursor->seat, event); +} + +static void handle_pointer_swipe_update(struct wl_listener *listener, void *data) { + struct sway_cursor *cursor = wl_container_of( + listener, cursor, swipe_update); + struct wlr_pointer_swipe_update_event *event = data; + cursor_handle_activity_from_device(cursor, &event->pointer->base); + seatop_swipe_update(cursor->seat, event); +} + +static void handle_pointer_swipe_end(struct wl_listener *listener, void *data) { + struct sway_cursor *cursor = wl_container_of( + listener, cursor, swipe_end); + struct wlr_pointer_swipe_end_event *event = data; + cursor_handle_activity_from_device(cursor, &event->pointer->base); + 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.pinch_begin, &cursor->pinch_begin); - cursor->pinch_update.notify = handle_pointer_pinch_update; - wl_signal_add(&wlr_cursor->events.pinch_update, &cursor->pinch_update); - cursor->pinch_end.notify = handle_pointer_pinch_end; - wl_signal_add(&wlr_cursor->events.pinch_end, &cursor->pinch_end); - cursor->swipe_begin.notify = handle_pointer_swipe_begin; - wl_signal_add(&wlr_cursor->events.swipe_begin, &cursor->swipe_begin); - cursor->swipe_update.notify = handle_pointer_swipe_update; - wl_signal_add(&wlr_cursor->events.swipe_update, &cursor->swipe_update); - cursor->swipe_end.notify = handle_pointer_swipe_end; - 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; + 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_begin.notify = handle_pointer_pinch_begin; + wl_signal_add(&wlr_cursor->events.pinch_update, &cursor->pinch_update); + cursor->pinch_update.notify = handle_pointer_pinch_update; + wl_signal_add(&wlr_cursor->events.pinch_end, &cursor->pinch_end); + cursor->pinch_end.notify = handle_pointer_pinch_end; + + wl_signal_add(&wlr_cursor->events.swipe_begin, &cursor->swipe_begin); + cursor->swipe_begin.notify = handle_pointer_swipe_begin; + wl_signal_add(&wlr_cursor->events.swipe_update, &cursor->swipe_update); + cursor->swipe_update.notify = handle_pointer_swipe_update; + wl_signal_add(&wlr_cursor->events.swipe_end, &cursor->swipe_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 #include #include +#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(¤t->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(¤t->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=] [--no-warn] \ +[:][:directions] + 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* This command is ignored and is only present for i3 compatibility. @@ -792,6 +848,11 @@ The default colors are: *unbindswitch* : Removes a binding for when changes to . +*unbindgesture* [--exact] [--input-device=] \ +[:][:directions] + Removes a binding for the specified _gesture_, _fingers_ + and _directions_ combination. + *unbindsym* [--whole-window] [--border] [--exclude-titlebar] [--release] [--locked] \ [--to-code] [--input-device=] Removes the binding for _key combo_ that was previously bound with the