diff --git a/common/gesture.c b/common/gesture.c index 8c2efe99..dbb8a5a8 100644 --- a/common/gesture.c +++ b/common/gesture.c @@ -50,6 +50,8 @@ 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]); @@ -117,11 +119,22 @@ 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"; } return NULL; } +int gesture_workspace_swipe_command_parse(char *cmd) { + if (strcmp(cmd, "normal") == 0) { + return -1; + } else if (strcmp(cmd, "invert") == 0) { + return 1; + } + return 0; +} + const char *gesture_direction_string(enum gesture_direction direction) { switch (direction) { case GESTURE_DIRECTION_NONE: @@ -314,6 +327,7 @@ struct gesture *gesture_tracker_end(struct gesture_tracker *tracker) { } __attribute__ ((fallthrough)); // Gestures with dx and dy + case GESTURE_TYPE_WORKSPACE_SWIPE: case GESTURE_TYPE_SWIPE: if (fabs(tracker->dx) > fabs(tracker->dy)) { if (tracker->dx > 0) { diff --git a/include/gesture.h b/include/gesture.h index 9c6b0f91..459053ba 100644 --- a/include/gesture.h +++ b/include/gesture.h @@ -12,11 +12,14 @@ enum gesture_type { GESTURE_TYPE_HOLD, GESTURE_TYPE_PINCH, GESTURE_TYPE_SWIPE, + GESTURE_TYPE_WORKSPACE_SWIPE, }; // 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; diff --git a/include/sway/output.h b/include/sway/output.h index 45e29ae6..04669dd0 100644 --- a/include/sway/output.h +++ b/include/sway/output.h @@ -202,6 +202,12 @@ void handle_output_manager_test(struct wl_listener *listener, void *data); void handle_output_power_manager_set_mode(struct wl_listener *listener, void *data); +void update_workspace_scroll_percent(int dx, int invert); + +void snap_workspace_scroll_percent(int dx, int invert); + +void reset_workspace_scroll_percent(); + struct sway_output_non_desktop *output_non_desktop_create(struct wlr_output *wlr_output); #endif diff --git a/sway/commands/gesture.c b/sway/commands/gesture.c index d4442cc3..a85b72d6 100644 --- a/sway/commands/gesture.c +++ b/sway/commands/gesture.c @@ -154,6 +154,19 @@ 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 invert", + bindtype, errmsg); + } + break; + default: + break; + } return gesture_binding_add(binding, argv[0], warn); } diff --git a/sway/desktop/output.c b/sway/desktop/output.c index 4d8d35f4..4389043a 100644 --- a/sway/desktop/output.c +++ b/sway/desktop/output.c @@ -31,6 +31,7 @@ #include "sway/tree/root.h" #include "sway/tree/view.h" #include "sway/tree/workspace.h" +#include "util.h" struct sway_output *output_by_name_or_id(const char *name_or_id) { for (int i = 0; i < root->outputs->length; ++i) { @@ -1103,3 +1104,95 @@ void handle_output_power_manager_set_mode(struct wl_listener *listener, oc = store_output_config(oc); apply_output_config(oc, output); } + +static void workspace_scroll_mark_dirty() { + // Damage all outputs + struct sway_output *soutput; + wl_list_for_each(soutput, &root->all_outputs, link) { + output_damage_whole(soutput); + } + transaction_commit_dirty(); +} + +void update_workspace_scroll_percent(int dx, int invert) { + struct sway_seat *seat = input_manager_get_default_seat(); + struct sway_workspace *focused_ws = seat_get_focused_workspace(seat); + struct sway_output *output = focused_ws->output; + + int visible_index = list_find(output->workspaces, focused_ws); + if (visible_index == -1) { + return; + } + + dx *= invert; + + // TODO: Make the speed factor configurable?? Works well on my trackpad... + // Maybe also take in account the width of the actual trackpad?? + const int SPEED_FACTOR = 500; + float percent = 0; + if (dx < 0) { + percent = (float) dx / SPEED_FACTOR; + } else if (dx > 0) { + percent = (float) dx / SPEED_FACTOR; + } else { + return; + } + + // Limit the percent depending on if the workspace is the first/last or in + // the middle somewhere. + int min = -1, max = 1; + if (visible_index + 1 >= output->workspaces->length) { + max = 0; + } + if (visible_index == 0) { + min = 0; + } + output->workspace_scroll_percent = MIN(max, MAX(min, percent)); + + workspace_scroll_mark_dirty(); +} + +void snap_workspace_scroll_percent(int dx, int invert) { + struct sway_seat *seat = input_manager_get_default_seat(); + struct sway_workspace *focused_ws = seat_get_focused_workspace(seat); + struct sway_output *output = focused_ws->output; + + // TODO: Make the threshold configurable?? + const float THRESHOLD = 0.35; + if (ABS(output->workspace_scroll_percent) <= THRESHOLD) { + goto reset; + } + + dx *= invert; + + int dir = 0; + if (dx < 0) { + dir = -1; + } else if (dx > 0) { + dir = 1; + } else { + goto reset; + } + + int visible_index = list_find(output->workspaces, focused_ws); + + size_t ws_index = wrap(visible_index + dir, output->workspaces->length); + struct sway_workspace *new_ws = output->workspaces->items[ws_index]; + sway_log(SWAY_DEBUG, "Switched to workspace: %s\n", new_ws->name); + workspace_switch(new_ws); + seat_consider_warp_to_focus(seat); + +reset: + // Reset the state + reset_workspace_scroll_percent(); +} + +void reset_workspace_scroll_percent() { + struct sway_seat *seat = input_manager_get_default_seat(); + struct sway_workspace *focused_ws = seat_get_focused_workspace(seat); + struct sway_output *output = focused_ws->output; + + output->workspace_scroll_percent = 0; + + workspace_scroll_mark_dirty(); +} diff --git a/sway/input/seatop_default.c b/sway/input/seatop_default.c index 84acefdf..4be967b9 100644 --- a/sway/input/seatop_default.c +++ b/sway/input/seatop_default.c @@ -1011,6 +1011,9 @@ 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)) { + struct seatop_default_event *seatop = seat->seatop_data; + gesture_tracker_begin(&seatop->gestures, GESTURE_TYPE_WORKSPACE_SWIPE, event->fingers); } else { // ... otherwise forward to client struct sway_cursor *cursor = seat->cursor; @@ -1028,6 +1031,41 @@ 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)) { + gesture_tracker_update(&seatop->gestures, + event->dx, event->dy, NAN, NAN); + + struct gesture_tracker *tracker = &seatop->gestures; + + struct sway_input_device *device = + event->pointer ? event->pointer->base.data : NULL; + // Determine name of input that received gesture + char *input = device + ? input_device_get_identifier(device->wlr_device) + : strdup("*"); + + struct gesture gesture = { + .fingers = tracker->fingers, + .type = tracker->type, + }; + if (fabs(tracker->dx) > fabs(tracker->dy)) { + if (tracker->dx > 0) { + gesture.directions |= GESTURE_DIRECTION_RIGHT; + } else { + gesture.directions |= GESTURE_DIRECTION_LEFT; + } + } else { + if (tracker->dy > 0) { + gesture.directions |= GESTURE_DIRECTION_DOWN; + } else { + gesture.directions |= GESTURE_DIRECTION_UP; + } + } + + struct sway_gesture_binding *current = gesture_binding_match(config->current_mode->gesture_bindings, &gesture, input); + + int invert = gesture_workspace_swipe_command_parse(current->command); + update_workspace_scroll_percent(seatop->gestures.dx, invert); } else { // ... otherwise forward to client struct sway_cursor *cursor = seat->cursor; @@ -1041,13 +1079,21 @@ 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)) { + if (!gesture_tracker_check(&seatop->gestures, GESTURE_TYPE_SWIPE) && + !gesture_tracker_check(&seatop->gestures, GESTURE_TYPE_WORKSPACE_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) { + switch (seatop->gestures.type) { + case GESTURE_TYPE_WORKSPACE_SWIPE: + reset_workspace_scroll_percent(); + break; + default: + break; + } gesture_tracker_cancel(&seatop->gestures); return; } @@ -1059,7 +1105,15 @@ static void handle_swipe_end(struct sway_seat *seat, &seatop->gestures, device); if (binding) { - gesture_binding_execute(seat, binding); + switch (binding->gesture.type) { + case GESTURE_TYPE_WORKSPACE_SWIPE:; + int invert = gesture_workspace_swipe_command_parse(binding->command); + snap_workspace_scroll_percent(seatop->gestures.dx, invert); + break; + default: + gesture_binding_execute(seat, binding); + break; + } } }