diff --git a/common/gesture.c b/common/gesture.c index 969521f9..f929c7fc 100644 --- a/common/gesture.c +++ b/common/gesture.c @@ -50,8 +50,6 @@ char *gesture_parse(const char *input, struct gesture *output) { output->type = GESTURE_TYPE_PINCH; } else if (strcmp(split->items[0], "swipe") == 0) { output->type = GESTURE_TYPE_SWIPE; - } else if (strcmp(split->items[0], "workspace_swipe") == 0) { - output->type = GESTURE_TYPE_WORKSPACE_SWIPE; } else { return strformat("expected hold|pinch|swipe, got %s", split->items[0]); @@ -96,10 +94,58 @@ char *gesture_parse(const char *input, struct gesture *output) { output->directions |= GESTURE_DIRECTION_CLOCKWISE; } else if (strcmp(item, "counterclockwise") == 0) { output->directions |= GESTURE_DIRECTION_COUNTERCLOCKWISE; - } else if (strcmp(item, "horizontal") == 0) { - output->directions |= GESTURE_DIRECTION_HORIZONTAL; + } 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; +} + +char *workspace_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 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, "horizontal") == 0) { + output->type = GESTURE_TYPE_WORKSPACE_SWIPE_HORIZONTAL; } else if (strcmp(item, "vertical") == 0) { - output->directions |= GESTURE_DIRECTION_VERTICAL; + output->type = GESTURE_TYPE_WORKSPACE_SWIPE_VERTICAL; } else { return strformat("expected direction, got %s", item); } @@ -123,22 +169,15 @@ const char *gesture_type_string(enum gesture_type type) { return "pinch"; case GESTURE_TYPE_SWIPE: return "swipe"; - case GESTURE_TYPE_WORKSPACE_SWIPE: - return "workspace_swipe"; + case GESTURE_TYPE_WORKSPACE_SWIPE_HORIZONTAL: + return "workspace_swipe_horizontal"; + case GESTURE_TYPE_WORKSPACE_SWIPE_VERTICAL: + return "workspace_swipe_vertical"; } return NULL; } -int gesture_workspace_swipe_command_parse(char *cmd) { - if (strcmp(cmd, "normal") == 0) { - return -1; - } else if (strcmp(cmd, "inverted") == 0) { - return 1; - } - return 0; -} - const char *gesture_direction_string(enum gesture_direction direction) { switch (direction) { case GESTURE_DIRECTION_NONE: @@ -159,10 +198,6 @@ const char *gesture_direction_string(enum gesture_direction direction) { return "clockwise"; case GESTURE_DIRECTION_COUNTERCLOCKWISE: return "counterclockwise"; - case GESTURE_DIRECTION_HORIZONTAL: - return "horizontal"; - case GESTURE_DIRECTION_VERTICAL: - return "vertical"; } return NULL; @@ -335,13 +370,6 @@ struct gesture *gesture_tracker_end(struct gesture_tracker *tracker) { } __attribute__ ((fallthrough)); // Gestures with dx and dy - case GESTURE_TYPE_WORKSPACE_SWIPE: - if (fabs(tracker->dx) > fabs(tracker->dy)) { - result->directions |= GESTURE_DIRECTION_HORIZONTAL; - } else { - result->directions |= GESTURE_DIRECTION_VERTICAL; - } - __attribute__ ((fallthrough)); case GESTURE_TYPE_SWIPE: if (fabs(tracker->dx) > fabs(tracker->dy)) { if (tracker->dx > 0) { @@ -358,6 +386,8 @@ struct gesture *gesture_tracker_end(struct gesture_tracker *tracker) { } // Gesture without any direction case GESTURE_TYPE_HOLD: + case GESTURE_TYPE_WORKSPACE_SWIPE_HORIZONTAL: + case GESTURE_TYPE_WORKSPACE_SWIPE_VERTICAL: break; // Not tracking any gesture case GESTURE_TYPE_NONE: diff --git a/include/gesture.h b/include/gesture.h index 3b3fd168..38e9ff6f 100644 --- a/include/gesture.h +++ b/include/gesture.h @@ -12,14 +12,13 @@ enum gesture_type { GESTURE_TYPE_HOLD, GESTURE_TYPE_PINCH, GESTURE_TYPE_SWIPE, - GESTURE_TYPE_WORKSPACE_SWIPE, + GESTURE_TYPE_WORKSPACE_SWIPE_HORIZONTAL, + GESTURE_TYPE_WORKSPACE_SWIPE_VERTICAL, }; // Turns single type enum value to constant string representation. const char *gesture_type_string(enum gesture_type direction); -int gesture_workspace_swipe_command_parse(char *cmd); - // Value to use to accept any finger count extern const uint8_t GESTURE_FINGERS_ANY; @@ -39,9 +38,6 @@ enum gesture_direction { // Directions based on rotation GESTURE_DIRECTION_CLOCKWISE = 1 << 6, GESTURE_DIRECTION_COUNTERCLOCKWISE = 1 << 7, - // Directions for workspace swipe - GESTURE_DIRECTION_HORIZONTAL = 1 << 8, - GESTURE_DIRECTION_VERTICAL = 1 << 9, }; // Turns single direction enum value to constant string representation. @@ -63,6 +59,13 @@ struct gesture { */ char *gesture_parse(const char *input, struct gesture *output); +/** + * Parses workspace gesture from [:][:] string. + * + * Return NULL on success, otherwise error message string + */ +char *workspace_gesture_parse(const char *input, struct gesture *output); + // Turns gesture into string representation char *gesture_to_string(struct gesture *gesture); diff --git a/include/sway/commands.h b/include/sway/commands.h index b8f5660a..4f2f16a9 100644 --- a/include/sway/commands.h +++ b/include/sway/commands.h @@ -114,6 +114,7 @@ sway_cmd cmd_bindcode; sway_cmd cmd_bindgesture; sway_cmd cmd_bindswitch; sway_cmd cmd_bindsym; +sway_cmd cmd_bindworkspacegesture; sway_cmd cmd_blur; sway_cmd cmd_blur_brightness; sway_cmd cmd_blur_contrast; @@ -223,6 +224,7 @@ sway_cmd cmd_unbindcode; sway_cmd cmd_unbindswitch; sway_cmd cmd_unbindgesture; sway_cmd cmd_unbindsym; +sway_cmd cmd_unbindworkspacegesture; sway_cmd cmd_unmark; sway_cmd cmd_urgent; sway_cmd cmd_workspace; diff --git a/include/sway/config.h b/include/sway/config.h index de49bbff..a2d39353 100644 --- a/include/sway/config.h +++ b/include/sway/config.h @@ -49,6 +49,7 @@ enum binding_flags { 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 + BINDING_INVERTED = 1 << 10, // workspace gesture only; gesture is inverted }; /** diff --git a/sway/commands.c b/sway/commands.c index 8b326547..461a1e74 100644 --- a/sway/commands.c +++ b/sway/commands.c @@ -49,6 +49,7 @@ static const struct cmd_handler handlers[] = { { "bindgesture", cmd_bindgesture }, { "bindswitch", cmd_bindswitch }, { "bindsym", cmd_bindsym }, + { "bindworkspacegesture", cmd_bindworkspacegesture }, { "blur", cmd_blur }, { "blur_brightness", cmd_blur_brightness }, { "blur_contrast", cmd_blur_contrast }, @@ -118,6 +119,7 @@ static const struct cmd_handler handlers[] = { { "unbindgesture", cmd_unbindgesture }, { "unbindswitch", cmd_unbindswitch }, { "unbindsym", cmd_unbindsym }, + { "unbindworkspacegesture", cmd_unbindworkspacegesture }, { "workspace", cmd_workspace }, { "workspace_auto_back_and_forth", cmd_ws_auto_back_and_forth }, }; diff --git a/sway/commands/gesture.c b/sway/commands/gesture.c index a75d54bb..1cc010a1 100644 --- a/sway/commands/gesture.c +++ b/sway/commands/gesture.c @@ -154,19 +154,6 @@ static struct cmd_results *cmd_bind_or_unbind_gesture(int argc, char **argv, boo return gesture_binding_remove(binding, argv[0]); } binding->command = join_args(argv + 1, argc - 1); - // Make sure that the gesture command is valid - switch (binding->gesture.type) { - case GESTURE_TYPE_WORKSPACE_SWIPE: - if (gesture_workspace_swipe_command_parse(binding->command) == 0) { - free(binding); - return cmd_results_new(CMD_FAILURE, - "Invalid %s command (%s). Either normal or inverted", - bindtype, errmsg); - } - break; - default: - break; - } return gesture_binding_add(binding, argv[0], warn); } @@ -177,3 +164,76 @@ struct cmd_results *cmd_bindgesture(int argc, char **argv) { struct cmd_results *cmd_unbindgesture(int argc, char **argv) { return cmd_bind_or_unbind_gesture(argc, argv, true); } + +/** + * Parse and execute bindgesture or unbindgesture command. + */ +static struct cmd_results *cmd_bind_or_unbind_workspacegesture(int argc, + char **argv, bool unbind) { + int minargs = 1; + char *bindtype = "bindgesture"; + if (unbind) { + 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 + binding->flags |= BINDING_EXACT; + while (argc > 0) { + if (strcmp("--inverted", argv[0]) == 0) { + binding->flags |= BINDING_INVERTED; + } 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 = workspace_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 = NULL; + return gesture_binding_add(binding, argv[0], warn); +} + +struct cmd_results *cmd_bindworkspacegesture(int argc, char **argv) { + return cmd_bind_or_unbind_workspacegesture(argc, argv, false); +} + +struct cmd_results *cmd_unbindworkspacegesture(int argc, char **argv) { + return cmd_bind_or_unbind_workspacegesture(argc, argv, true); +} diff --git a/sway/input/seatop_default.c b/sway/input/seatop_default.c index f88f1c55..0313b80b 100644 --- a/sway/input/seatop_default.c +++ b/sway/input/seatop_default.c @@ -1011,9 +1011,12 @@ static void handle_swipe_begin(struct sway_seat *seat, 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 if (gesture_binding_check(bindings, GESTURE_TYPE_WORKSPACE_SWIPE, event->fingers, device)) { + } else if (gesture_binding_check(bindings, GESTURE_TYPE_WORKSPACE_SWIPE_HORIZONTAL, event->fingers, device)) { struct seatop_default_event *seatop = seat->seatop_data; - gesture_tracker_begin(&seatop->gestures, GESTURE_TYPE_WORKSPACE_SWIPE, event->fingers); + gesture_tracker_begin(&seatop->gestures, GESTURE_TYPE_WORKSPACE_SWIPE_HORIZONTAL, event->fingers); + } else if (gesture_binding_check(bindings, GESTURE_TYPE_WORKSPACE_SWIPE_VERTICAL, event->fingers, device)) { + struct seatop_default_event *seatop = seat->seatop_data; + gesture_tracker_begin(&seatop->gestures, GESTURE_TYPE_WORKSPACE_SWIPE_VERTICAL, event->fingers); } else { // ... otherwise forward to client struct sway_cursor *cursor = seat->cursor; @@ -1031,10 +1034,12 @@ static void handle_swipe_update(struct sway_seat *seat, if (gesture_tracker_check(&seatop->gestures, GESTURE_TYPE_SWIPE)) { gesture_tracker_update(&seatop->gestures, event->dx, event->dy, NAN, NAN); - } else if (gesture_tracker_check(&seatop->gestures, GESTURE_TYPE_WORKSPACE_SWIPE)) { + } else if (gesture_tracker_check(&seatop->gestures, GESTURE_TYPE_WORKSPACE_SWIPE_HORIZONTAL) || + gesture_tracker_check(&seatop->gestures, GESTURE_TYPE_WORKSPACE_SWIPE_VERTICAL)) { gesture_tracker_update(&seatop->gestures, event->dx, event->dy, NAN, NAN); + // Find the gesture and update the swipe percentage struct gesture_tracker *tracker = &seatop->gestures; struct sway_input_device *device = event->pointer ? event->pointer->base.data : NULL; @@ -1046,22 +1051,24 @@ static void handle_swipe_update(struct sway_seat *seat, struct gesture gesture = { .fingers = tracker->fingers, .type = tracker->type, - .directions = fabs(tracker->dx) > fabs(tracker->dy) - ? GESTURE_DIRECTION_HORIZONTAL : GESTURE_DIRECTION_VERTICAL, + .directions = GESTURE_DIRECTION_NONE, }; - struct sway_gesture_binding *binding = gesture_binding_match( config->current_mode->gesture_bindings, &gesture, input); + if (binding) { - int invert = gesture_workspace_swipe_command_parse(binding->command); - if ((binding->gesture.directions & GESTURE_DIRECTION_VERTICAL) == - GESTURE_DIRECTION_VERTICAL) { - update_workspace_scroll_percent(seat, tracker->dy, invert, - SWIPE_GESTURE_DIRECTION_VERTICAL); - } else if ((binding->gesture.directions & GESTURE_DIRECTION_HORIZONTAL) == - GESTURE_DIRECTION_HORIZONTAL) { + int invert = binding->flags & BINDING_INVERTED ? 1 : -1; + switch (binding->gesture.type) { + case GESTURE_TYPE_WORKSPACE_SWIPE_HORIZONTAL: update_workspace_scroll_percent(seat, tracker->dx, invert, SWIPE_GESTURE_DIRECTION_HORIZONTAL); + break; + case GESTURE_TYPE_WORKSPACE_SWIPE_VERTICAL: + update_workspace_scroll_percent(seat, tracker->dy, invert, + SWIPE_GESTURE_DIRECTION_VERTICAL); + break; + default: + break; } } } else { @@ -1078,13 +1085,16 @@ static void handle_swipe_end(struct sway_seat *seat, // 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) && - !gesture_tracker_check(&seatop->gestures, GESTURE_TYPE_WORKSPACE_SWIPE)) { + !gesture_tracker_check(&seatop->gestures, GESTURE_TYPE_WORKSPACE_SWIPE_HORIZONTAL) && + !gesture_tracker_check(&seatop->gestures, GESTURE_TYPE_WORKSPACE_SWIPE_VERTICAL)) { 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 && seatop->gestures.type != GESTURE_TYPE_WORKSPACE_SWIPE) { + if (event->cancelled && + seatop->gestures.type != GESTURE_TYPE_WORKSPACE_SWIPE_HORIZONTAL + && seatop->gestures.type != GESTURE_TYPE_WORKSPACE_SWIPE_VERTICAL) { gesture_tracker_cancel(&seatop->gestures); return; } @@ -1097,7 +1107,8 @@ static void handle_swipe_end(struct sway_seat *seat, if (binding) { switch (binding->gesture.type) { - case GESTURE_TYPE_WORKSPACE_SWIPE:; + case GESTURE_TYPE_WORKSPACE_SWIPE_HORIZONTAL: + case GESTURE_TYPE_WORKSPACE_SWIPE_VERTICAL: snap_workspace_scroll_percent(seat); break; default: diff --git a/sway/sway.5.scd b/sway/sway.5.scd index b0f583f2..24db1b38 100644 --- a/sway/sway.5.scd +++ b/sway/sway.5.scd @@ -547,6 +547,47 @@ runtime. ``` +*bindworkspacegesture* [--input-device=] [--no-warn] \ +[--inverted] [][:direction] + Binds the swipe to switch workspaces in a 1:1 fashion, but only when + detected. Optionally can be limited to bind to a certain number of + _fingers_ or to certain _directions_. + +[[ *type* +:[ *fingers* +:< *direction* +| swipe +: 3 - 5 +: horizontal, vertical + + The _fingers_ can be limited to any sensible number or left empty to accept + any finger counts. + Valid directions are _horizontal_ and _vertical_. + + 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. + + If _inverted_ is given, the swipe direction will be reversed. + + The priority for matching bindings is as follows: input device, then + exact matches. + + 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 + bindworkspacegesture 4:horizontal + # Allow switching between workspaces with up and down swipes + bindworkspacegesture 3:vertical + # Allow switching between workspaces with up and down swipes but inverted + bindworkspacegesture --inverted 3:vertical + +``` + *client.background* This command is ignored and is only present for i3 compatibility.