Compare commits

...

43 commits

Author SHA1 Message Date
Alex Janka a81e80fe68 check for null pointer when getting gaps 2024-08-07 11:10:47 +10:00
Alex Janka 5293fb9206 editor settings 2024-08-07 11:10:47 +10:00
Alex Janka d01f16d09c resize by dragging gaps 2024-08-07 11:10:47 +10:00
Aidan Dang 59a10cf4c7 Add primary_selection config option
See: https://github.com/swaywm/sway/issues/4511

Adds a bool config option `primary_selection`, which explicitly
enables/disables the primary selection clipboard. Defaults to enabled.

This is implemented as a launch-only option which enables or disables the creation of the
`zwp_primary_selection_device_manager_v1` global.

Co-authored-by: Tilde Rose <t1lde@protonmail.com>
2024-08-07 11:10:47 +10:00
Erik Reider 59c2b7a884 Merge branch 'master' into workspace-movement 2024-08-06 23:33:00 +02:00
Erik Reider 4d7ca7d975
Fixed blur opacity. Fixes #338 (#339) 2024-07-29 12:13:11 -04:00
Erik Reider 358d649412 Merge branch 'master' into workspace-movement 2024-05-06 21:13:41 +02:00
Erik Reider 5ace6bb7bd Fixed compilation issues on Nix 2024-01-26 23:20:05 +01:00
Erik Reider 60ecc42e42 Added comment about workspace_gesture_parse reasoning 2024-01-26 19:50:24 +01:00
Erik Reider dec948dc80 Moved easing functions into their own file 2024-01-26 19:46:13 +01:00
Erik Reider 8193bb6e4b Fixed switch-statement nit 2024-01-26 19:38:19 +01:00
Erik Reider 84c1b9c7f3 Removed unneeded condition check in render_floating func 2024-01-26 19:33:11 +01:00
Erik Reider 5bb8877abe Made fullscreen rendering a bit more clear 2024-01-26 19:32:32 +01:00
Erik Reider d7ca31c596 Removed for-loop in render_workspace 2024-01-26 19:29:28 +01:00
Erik Reider 4f9ac2dfbb Fixed tracker value being updated even if percent isn't
Fixes long swipes still updating the tracker value even if the percent wasn't changed
2024-01-25 19:49:12 +01:00
Erik Reider b491ac42b0 Code cleanuo: Simplified adjust_* functions 2024-01-25 18:59:43 +01:00
Erik Reider 1fedb1a1ee Reset swipe on other cursor rebase 2024-01-24 11:13:30 +01:00
Erik Reider 0da822151d Unset focus while swiping 2024-01-24 03:35:02 +01:00
Erik Reider a12a8ea7b0 Merged workspace_output_next and workspace_output_next_wrap 2024-01-24 03:11:39 +01:00
Erik Reider 7214db9c2d Updated default threshold to 50% 2024-01-24 02:29:55 +01:00
Erik Reider bd3745c6ed Draw rect and shadow on "Spring effect" for fullscreen views 2024-01-24 02:29:55 +01:00
Erik Reider 83e0872e79 Fixed regular views rendering behind fullscreen ones + code cleanup 2024-01-24 02:17:44 +01:00
Erik Reider 0a619a03ab Use Ease-Out when swiping into the non-wrapped edges 2024-01-23 18:55:12 +01:00
Erik Reider 54daaa9ed2 Updated swipe percentage calculation to also use avg_velocity 2024-01-23 18:54:46 +01:00
Erik Reider 42fcd6c86d Fixed fullscreen sometimes segfaulting 2024-01-22 19:18:31 +01:00
Erik Reider 288b04d740 Fixed gesture detecting every finger combination instead of the specified one 2024-01-22 18:58:35 +01:00
Erik Reider 1113e5653a Added workspace_gesture_threshold config option 2024-01-22 18:25:07 +01:00
Erik Reider 0b521abe88 Added workspace_gesture_wrap_around config option 2024-01-22 18:25:07 +01:00
Erik Reider 519bfc4ecc Added workspace_gesture_spring_size config option 2024-01-22 18:02:32 +01:00
Erik Reider 3480479f17 Moved to bindworkspacegesture instead of bindgesture 2024-01-22 17:47:52 +01:00
Erik Reider 067613854b Added vertical gesture support 2024-01-22 16:40:02 +01:00
Erik Reider fdf764d3f6 Added "Spring effect" when trying to swipe past limits 2024-01-22 15:39:34 +01:00
Erik Reider 29c481da95 Workspace code cleanup 2024-01-22 15:39:34 +01:00
Erik Reider 3ad50f9627 Clear transformed fullscreen damage 2024-01-22 14:28:14 +01:00
Erik Reider 8da7c5080c Fixed popups not compensating for transition 2024-01-22 13:19:32 +01:00
Erik Reider 9e551680db Try switching workspace on gesture cancel instead of reset 2024-01-22 12:21:57 +01:00
Erik Reider 8afad34fd5 Render fullscreen containers over all layers 2024-01-22 12:17:10 +01:00
Erik Reider 8440285e8d Only damage the focused output 2024-01-22 12:14:45 +01:00
Erik Reider 44f54b9933 Removed workspace_offset command 2024-01-21 20:01:39 +01:00
Erik Reider 0c6d69b7fe Added gesture support 2024-01-21 20:01:11 +01:00
Erik Reider 6552778dd6 Fixed scroll not resetting to 0 when manually switching ws 2024-01-21 19:54:24 +01:00
Erik Reider 0b86078eea Fixed transition not compensating for workspace gaps 2024-01-21 19:54:03 +01:00
Erik Reider 4e9e7c8d1a Initial implementation of workspace movement (without gestures) 2024-01-21 13:53:11 +01:00
23 changed files with 992 additions and 155 deletions

1
.envrc Normal file
View file

@ -0,0 +1 @@
export PKG_CONFIG_PATH=/usr/lib/wlroots0.17/pkgconfig

8
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,8 @@
{
"editor.formatOnSave": false,
"C_Cpp.default.compileCommands": "/home/alex/forks/swayfx/builddir/compile_commands.json",
"C_Cpp.default.configurationProvider": "mesonbuild.mesonbuild",
"files.associations": {
"*.h": "c"
}
}

View file

@ -90,6 +90,60 @@ char *gesture_parse(const char *input, struct gesture *output) {
return NULL; return NULL;
} }
// Similar to gesture_parse but with fewer checks to match the different
// workspace_gesture syntax
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 > 2) {
return format_str(
"expected [:<fingers>][:direction], got %s",
input);
}
// Parse optional arguments
if (split->length > 1) {
char *next = split->items[0];
// 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 == 2 ? split->items[1] : NULL;
} else if (split->length == 2) {
// Fail here if argument can only be finger count
return format_str("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->type = GESTURE_TYPE_WORKSPACE_SWIPE_VERTICAL;
} else {
return format_str("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) { const char *gesture_type_string(enum gesture_type type) {
switch (type) { switch (type) {
case GESTURE_TYPE_NONE: case GESTURE_TYPE_NONE:
@ -100,6 +154,10 @@ const char *gesture_type_string(enum gesture_type type) {
return "pinch"; return "pinch";
case GESTURE_TYPE_SWIPE: case GESTURE_TYPE_SWIPE:
return "swipe"; return "swipe";
case GESTURE_TYPE_WORKSPACE_SWIPE_HORIZONTAL:
return "workspace_swipe_horizontal";
case GESTURE_TYPE_WORKSPACE_SWIPE_VERTICAL:
return "workspace_swipe_vertical";
} }
return NULL; return NULL;
@ -313,6 +371,8 @@ struct gesture *gesture_tracker_end(struct gesture_tracker *tracker) {
} }
// Gesture without any direction // Gesture without any direction
case GESTURE_TYPE_HOLD: case GESTURE_TYPE_HOLD:
case GESTURE_TYPE_WORKSPACE_SWIPE_HORIZONTAL:
case GESTURE_TYPE_WORKSPACE_SWIPE_VERTICAL:
break; break;
// Not tracking any gesture // Not tracking any gesture
case GESTURE_TYPE_NONE: case GESTURE_TYPE_NONE:

View file

@ -12,6 +12,8 @@ enum gesture_type {
GESTURE_TYPE_HOLD, GESTURE_TYPE_HOLD,
GESTURE_TYPE_PINCH, GESTURE_TYPE_PINCH,
GESTURE_TYPE_SWIPE, GESTURE_TYPE_SWIPE,
GESTURE_TYPE_WORKSPACE_SWIPE_HORIZONTAL,
GESTURE_TYPE_WORKSPACE_SWIPE_VERTICAL,
}; };
// Turns single type enum value to constant string representation. // Turns single type enum value to constant string representation.
@ -57,6 +59,13 @@ struct gesture {
*/ */
char *gesture_parse(const char *input, struct gesture *output); char *gesture_parse(const char *input, struct gesture *output);
/**
* Parses workspace gesture from [:<fingers>][:<directions>] string.
*
* Return NULL on success, otherwise error message string
*/
char *workspace_gesture_parse(const char *input, struct gesture *output);
// Turns gesture into string representation // Turns gesture into string representation
char *gesture_to_string(struct gesture *gesture); char *gesture_to_string(struct gesture *gesture);

View file

@ -115,6 +115,7 @@ sway_cmd cmd_bindcode;
sway_cmd cmd_bindgesture; sway_cmd cmd_bindgesture;
sway_cmd cmd_bindswitch; sway_cmd cmd_bindswitch;
sway_cmd cmd_bindsym; sway_cmd cmd_bindsym;
sway_cmd cmd_bindworkspacegesture;
sway_cmd cmd_blur; sway_cmd cmd_blur;
sway_cmd cmd_blur_brightness; sway_cmd cmd_blur_brightness;
sway_cmd cmd_blur_contrast; sway_cmd cmd_blur_contrast;
@ -223,11 +224,15 @@ sway_cmd cmd_unbindcode;
sway_cmd cmd_unbindswitch; sway_cmd cmd_unbindswitch;
sway_cmd cmd_unbindgesture; sway_cmd cmd_unbindgesture;
sway_cmd cmd_unbindsym; sway_cmd cmd_unbindsym;
sway_cmd cmd_unbindworkspacegesture;
sway_cmd cmd_unmark; sway_cmd cmd_unmark;
sway_cmd cmd_urgent; sway_cmd cmd_urgent;
sway_cmd cmd_workspace; sway_cmd cmd_workspace;
sway_cmd cmd_workspace_layout; sway_cmd cmd_workspace_layout;
sway_cmd cmd_ws_auto_back_and_forth; sway_cmd cmd_ws_auto_back_and_forth;
sway_cmd cmd_ws_gesture_spring_size;
sway_cmd cmd_ws_gesture_threshold;
sway_cmd cmd_ws_gesture_wrap_around;
sway_cmd cmd_xwayland; sway_cmd cmd_xwayland;
sway_cmd bar_cmd_bindcode; sway_cmd bar_cmd_bindcode;

View file

@ -51,6 +51,7 @@ enum binding_flags {
BINDING_INHIBITED = 1 << 7, // keyboard only: ignore shortcut inhibitor BINDING_INHIBITED = 1 << 7, // keyboard only: ignore shortcut inhibitor
BINDING_NOREPEAT = 1 << 8, // keyboard only; do not trigger when repeating a held key 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_EXACT = 1 << 9, // gesture only; only trigger on exact match
BINDING_INVERTED = 1 << 10, // workspace gesture only; gesture is inverted
}; };
/** /**
@ -501,6 +502,10 @@ struct sway_config {
bool titlebar_separator; bool titlebar_separator;
bool scratchpad_minimize; bool scratchpad_minimize;
int workspace_gesture_spring_size;
bool workspace_gesture_wrap_around;
float workspace_gesture_threshold;
list_t *layer_criteria; list_t *layer_criteria;
char *swaynag_command; char *swaynag_command;

View file

@ -0,0 +1,10 @@
#ifndef ANIMATION_UTILS_H
#define ANIMATION_UTILS_H
double lerp (double a, double b, double t);
double ease_out_cubic (double t);
// TODO: Add more easing functions in the future like ease_in and ease_in_out, etc...
#endif

View file

@ -15,6 +15,19 @@ struct decoration_data get_undecorated_decoration_data();
struct sway_server; struct sway_server;
struct sway_container; struct sway_container;
enum swipe_gesture_direction {
SWIPE_GESTURE_DIRECTION_NONE,
SWIPE_GESTURE_DIRECTION_HORIZONTAL,
SWIPE_GESTURE_DIRECTION_VERTICAL,
};
struct workspace_scroll {
double percent;
double avg_velocity;
int num_updates;
enum swipe_gesture_direction direction;
};
struct decoration_data { struct decoration_data {
float alpha; float alpha;
float saturation; float saturation;
@ -32,6 +45,7 @@ struct render_data {
pixman_region32_t *damage; pixman_region32_t *damage;
struct wlr_box *clip_box; struct wlr_box *clip_box;
struct decoration_data deco_data; struct decoration_data deco_data;
bool on_focused_workspace;
struct sway_view *view; struct sway_view *view;
}; };
@ -71,6 +85,8 @@ struct sway_output {
struct wl_listener needs_frame; struct wl_listener needs_frame;
struct wl_listener request_state; struct wl_listener request_state;
struct workspace_scroll workspace_scroll;
struct { struct {
struct wl_signal disable; struct wl_signal disable;
} events; } events;
@ -227,6 +243,20 @@ void handle_output_manager_test(struct wl_listener *listener, void *data);
void handle_output_power_manager_set_mode(struct wl_listener *listener, void handle_output_power_manager_set_mode(struct wl_listener *listener,
void *data); void *data);
struct workspace_scroll workspace_scroll_get_default();
bool workspace_scroll_equal(struct workspace_scroll *a, struct workspace_scroll *b);
void workspace_scroll_begin(struct sway_seat *seat,
enum swipe_gesture_direction direction);
void workspace_scroll_update(struct sway_seat *seat, struct gesture_tracker *tracker,
struct wlr_pointer_swipe_update_event *event, int invert);
void workspace_scroll_end(struct sway_seat *seat);
void workspace_scroll_reset(struct sway_seat *seat, struct sway_workspace *ws);
struct sway_output_non_desktop *output_non_desktop_create(struct wlr_output *wlr_output); struct sway_output_non_desktop *output_non_desktop_create(struct wlr_output *wlr_output);
#endif #endif

View file

@ -70,11 +70,13 @@ struct sway_workspace *workspace_by_number(const char* name);
struct sway_workspace *workspace_by_name(const char*); struct sway_workspace *workspace_by_name(const char*);
struct sway_workspace *workspace_output_next(struct sway_workspace *current); struct sway_workspace *workspace_output_next(struct sway_workspace *current,
bool should_wrap);
struct sway_workspace *workspace_next(struct sway_workspace *current); struct sway_workspace *workspace_next(struct sway_workspace *current);
struct sway_workspace *workspace_output_prev(struct sway_workspace *current); struct sway_workspace *workspace_output_prev(struct sway_workspace *current,
bool should_wrap);
struct sway_workspace *workspace_prev(struct sway_workspace *current); struct sway_workspace *workspace_prev(struct sway_workspace *current);

View file

@ -49,6 +49,7 @@ static const struct cmd_handler handlers[] = {
{ "bindgesture", cmd_bindgesture }, { "bindgesture", cmd_bindgesture },
{ "bindswitch", cmd_bindswitch }, { "bindswitch", cmd_bindswitch },
{ "bindsym", cmd_bindsym }, { "bindsym", cmd_bindsym },
{ "bindworkspacegesture", cmd_bindworkspacegesture },
{ "blur", cmd_blur }, { "blur", cmd_blur },
{ "blur_brightness", cmd_blur_brightness }, { "blur_brightness", cmd_blur_brightness },
{ "blur_contrast", cmd_blur_contrast }, { "blur_contrast", cmd_blur_contrast },
@ -96,6 +97,7 @@ static const struct cmd_handler handlers[] = {
{ "no_focus", cmd_no_focus }, { "no_focus", cmd_no_focus },
{ "output", cmd_output }, { "output", cmd_output },
{ "popup_during_fullscreen", cmd_popup_during_fullscreen }, { "popup_during_fullscreen", cmd_popup_during_fullscreen },
{ "primary_selection", cmd_primary_selection },
{ "seat", cmd_seat }, { "seat", cmd_seat },
{ "set", cmd_set }, { "set", cmd_set },
{ "shadow_blur_radius", cmd_shadow_blur_radius }, { "shadow_blur_radius", cmd_shadow_blur_radius },
@ -118,8 +120,12 @@ static const struct cmd_handler handlers[] = {
{ "unbindgesture", cmd_unbindgesture }, { "unbindgesture", cmd_unbindgesture },
{ "unbindswitch", cmd_unbindswitch }, { "unbindswitch", cmd_unbindswitch },
{ "unbindsym", cmd_unbindsym }, { "unbindsym", cmd_unbindsym },
{ "unbindworkspacegesture", cmd_unbindworkspacegesture },
{ "workspace", cmd_workspace }, { "workspace", cmd_workspace },
{ "workspace_auto_back_and_forth", cmd_ws_auto_back_and_forth }, { "workspace_auto_back_and_forth", cmd_ws_auto_back_and_forth },
{ "workspace_gesture_spring_size", cmd_ws_gesture_spring_size },
{ "workspace_gesture_threshold", cmd_ws_gesture_threshold },
{ "workspace_gesture_wrap_around", cmd_ws_gesture_wrap_around },
}; };
/* Config-time only commands. Keep alphabetized */ /* Config-time only commands. Keep alphabetized */

View file

@ -164,3 +164,76 @@ struct cmd_results *cmd_bindgesture(int argc, char **argv) {
struct cmd_results *cmd_unbindgesture(int argc, char **argv) { struct cmd_results *cmd_unbindgesture(int argc, char **argv) {
return cmd_bind_or_unbind_gesture(argc, argv, true); 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);
}

View file

@ -6,10 +6,12 @@
#include "sway/commands.h" #include "sway/commands.h"
#include "sway/config.h" #include "sway/config.h"
#include "sway/input/seat.h" #include "sway/input/seat.h"
#include "sway/output.h"
#include "sway/tree/workspace.h" #include "sway/tree/workspace.h"
#include "list.h" #include "list.h"
#include "log.h" #include "log.h"
#include "stringop.h" #include "stringop.h"
#include "util.h"
static struct workspace_config *workspace_config_find_or_create(char *ws_name) { static struct workspace_config *workspace_config_find_or_create(char *ws_name) {
struct workspace_config *wsc = workspace_find_config(ws_name); struct workspace_config *wsc = workspace_find_config(ws_name);

View file

@ -0,0 +1,49 @@
#define _POSIX_C_SOURCE 200809L
#include "sway/commands.h"
#include "sway/config.h"
#include "util.h"
struct cmd_results *cmd_ws_gesture_spring_size(int argc, char **argv) {
struct cmd_results *error = NULL;
if ((error = checkarg(argc, "workspace_gesture_spring_size", EXPECTED_EQUAL_TO, 1))) {
return error;
}
char *inv;
int value = strtol(argv[0], &inv, 10);
if (*inv != '\0' || value < 0 || value > 250) {
return cmd_results_new(CMD_FAILURE, "Invalid size specified");
}
config->workspace_gesture_spring_size = value;
return cmd_results_new(CMD_SUCCESS, NULL);
}
struct cmd_results *cmd_ws_gesture_wrap_around(int argc, char **argv) {
struct cmd_results *error = NULL;
if ((error = checkarg(argc, "workspace_gesture_wrap_around", EXPECTED_EQUAL_TO, 1))) {
return error;
}
config->workspace_gesture_wrap_around = parse_boolean(argv[0], true);
return cmd_results_new(CMD_SUCCESS, NULL);
}
struct cmd_results *cmd_ws_gesture_threshold(int argc, char **argv) {
struct cmd_results *error = NULL;
if ((error = checkarg(argc, "workspace_gesture_threshold", EXPECTED_EQUAL_TO, 1))) {
return error;
}
char *err;
float val = strtof(argv[0], &err);
if (*err || val < 0.1f || val > 0.9f) {
return cmd_results_new(CMD_INVALID, "workspace_gesture_threshold float invalid. "
"Should be between 0.1 and 0.9");
}
config->workspace_gesture_threshold = val;
return cmd_results_new(CMD_SUCCESS, NULL);
}

View file

@ -369,6 +369,10 @@ static void config_defaults(struct sway_config *config) {
config->titlebar_separator = true; config->titlebar_separator = true;
config->scratchpad_minimize = false; config->scratchpad_minimize = false;
config->workspace_gesture_spring_size = 50;
config->workspace_gesture_wrap_around = false;
config->workspace_gesture_threshold = 0.5;
if (!(config->layer_criteria = create_list())) goto cleanup; if (!(config->layer_criteria = create_list())) goto cleanup;
// The keysym to keycode translation // The keysym to keycode translation

View file

@ -23,6 +23,7 @@
#include "log.h" #include "log.h"
#include "scenefx/render/pass.h" #include "scenefx/render/pass.h"
#include "sway/config.h" #include "sway/config.h"
#include "sway/fx_util/animation_utils.h"
#include "sway/desktop/transaction.h" #include "sway/desktop/transaction.h"
#include "sway/input/input-manager.h" #include "sway/input/input-manager.h"
#include "sway/input/seat.h" #include "sway/input/seat.h"
@ -36,6 +37,10 @@
#include "sway/tree/root.h" #include "sway/tree/root.h"
#include "sway/tree/view.h" #include "sway/tree/view.h"
#include "sway/tree/workspace.h" #include "sway/tree/workspace.h"
#include "util.h"
#define PREV_WS_LIMIT -1.0f
#define NEXT_WS_LIMIT 1.0f
#if WLR_HAS_DRM_BACKEND #if WLR_HAS_DRM_BACKEND
#include <wlr/backend/drm.h> #include <wlr/backend/drm.h>
@ -615,7 +620,8 @@ static int output_repaint_timer_handler(void *data) {
if (fullscreen_con && fullscreen_con->view && !debug.noscanout if (fullscreen_con && fullscreen_con->view && !debug.noscanout
// Only output to monitor without compositing when saturation is changed // Only output to monitor without compositing when saturation is changed
&& fullscreen_con->saturation == 1.0f) { && fullscreen_con->saturation == 1.0f &&
output->workspace_scroll.percent == 0.0f) {
// Try to scan-out the fullscreen view // Try to scan-out the fullscreen view
static bool last_scanned_out = false; static bool last_scanned_out = false;
bool scanned_out = bool scanned_out =
@ -1114,6 +1120,8 @@ void handle_new_output(struct wl_listener *listener, void *data) {
wlr_output_transformed_resolution(output->wlr_output, &width, &height); wlr_output_transformed_resolution(output->wlr_output, &width, &height);
wlr_damage_ring_set_bounds(&output->damage_ring, width, height); wlr_damage_ring_set_bounds(&output->damage_ring, width, height);
update_output_manager_config(server); update_output_manager_config(server);
output->workspace_scroll = workspace_scroll_get_default();
} }
void handle_output_layout_change(struct wl_listener *listener, void handle_output_layout_change(struct wl_listener *listener,
@ -1241,3 +1249,166 @@ void handle_output_power_manager_set_mode(struct wl_listener *listener,
oc = store_output_config(oc); oc = store_output_config(oc);
apply_output_config(oc, output); apply_output_config(oc, output);
} }
struct workspace_scroll workspace_scroll_get_default() {
return (struct workspace_scroll) {
.percent = 0,
.avg_velocity = 0,
.num_updates = 0,
.direction = SWIPE_GESTURE_DIRECTION_NONE,
};
}
bool workspace_scroll_equal(struct workspace_scroll *a, struct workspace_scroll *b) {
return a->avg_velocity == b->avg_velocity &&
a->direction == b->direction &&
a->num_updates == b->num_updates &&
a->percent == b->percent;
}
void workspace_scroll_begin(struct sway_seat *seat,
enum swipe_gesture_direction direction) {
struct sway_workspace *focused_ws = seat_get_focused_workspace(seat);
struct sway_output *output = focused_ws->output;
// Reset the state
output->workspace_scroll = workspace_scroll_get_default();
output->workspace_scroll.direction = direction;
output_damage_whole(output);
transaction_commit_dirty();
// Unset focus
seat_set_focus_workspace(seat, NULL);
}
void workspace_scroll_update(struct sway_seat *seat, struct gesture_tracker *tracker,
struct wlr_pointer_swipe_update_event *event, int invert) {
double delta_sum;
enum swipe_gesture_direction direction;
switch (tracker->type) {
case GESTURE_TYPE_WORKSPACE_SWIPE_HORIZONTAL:
direction = SWIPE_GESTURE_DIRECTION_HORIZONTAL;
delta_sum = tracker->dx + event->dx * invert;
break;
case GESTURE_TYPE_WORKSPACE_SWIPE_VERTICAL:
direction = SWIPE_GESTURE_DIRECTION_VERTICAL;
delta_sum = tracker->dy + event->dy * invert;
break;
default:
return;
}
struct sway_workspace *focused_ws = seat_get_focused_workspace(seat);
struct sway_output *output = focused_ws->output;
struct workspace_scroll *ws_scroll = &output->workspace_scroll;
if (direction != ws_scroll->direction) {
return;
}
int visible_index = list_find(output->workspaces, focused_ws);
if (visible_index == -1) {
return;
}
// Get the updated average velocity
ws_scroll->avg_velocity = fabs(delta_sum) / (++ws_scroll->num_updates);
// TODO: Make configurable
const int SPEED_FACTOR = 750;
double percent = delta_sum / SPEED_FACTOR;
double min = PREV_WS_LIMIT, max = NEXT_WS_LIMIT;
if (!config->workspace_gesture_wrap_around) {
// Visualized to the user that this is the last / first workspace by
// allowing a small eased swipe, a "Spring effect"
double spring_limit = (double) config->workspace_gesture_spring_size /
output->width * output->wlr_output->scale;
// Make sure that the limit is always smaller than the threshold to
// avoid accidental workspace switches
double small_threshold = MAX(config->workspace_gesture_threshold - 0.1, 0);
spring_limit = MIN(small_threshold, spring_limit);
// Limit the percent depending on if the workspace is the first/last or in
// the middle somewhere. Uses ease_out to make the limit feel more natural.
if (visible_index + 1 >= output->workspaces->length) {
max = spring_limit;
if (percent > 0) {
percent = lerp(0, max, ease_out_cubic(fabs(percent)));
min = 0;
}
}
if (visible_index == 0) {
min = -spring_limit;
if (percent < 0) {
percent = lerp(0, min, ease_out_cubic(fabs(percent)));
max = 0;
}
}
}
// Update the tracker data if we aren't exceeding the max swipe limit
if (percent < max && percent > min) {
tracker->dx += event->dx * invert;
tracker->dy += event->dy * invert;
}
ws_scroll->percent = CLAMP(percent, min, max);
ws_scroll->direction = direction;
output_damage_whole(output);
transaction_commit_dirty();
}
void workspace_scroll_end(struct sway_seat *seat) {
struct sway_workspace *focused_ws = seat_get_focused_workspace(seat);
struct sway_output *output = focused_ws->output;
struct workspace_scroll *ws_scroll = &output->workspace_scroll;
int visible_index = list_find(output->workspaces, focused_ws);
bool not_edge_ws = config->workspace_gesture_wrap_around;
int dir;
if (ws_scroll->percent < 0) {
dir = PREV_WS_LIMIT;
not_edge_ws |= visible_index > 0;
} else if (ws_scroll->percent > 0) {
dir = NEXT_WS_LIMIT;
not_edge_ws |= visible_index + 1 < output->workspaces->length;
} else {
// Skip setting workspace if the percentage is zero
goto reset_state;
}
// TODO: Make configurable
const int VELOCITY_NEEDED = 8;
// Only switch workspaces when the percent exceeds the threshold or if
// the avg_speed exceeds the limit (for fast but short swipes).
bool threshold_met = fabs(ws_scroll->percent) >= config->workspace_gesture_threshold;
bool enough_velocity = ws_scroll->avg_velocity >= VELOCITY_NEEDED && not_edge_ws;
if (!threshold_met && !enough_velocity) {
goto reset_state;
}
size_t ws_index = wrap(visible_index + dir, output->workspaces->length);
focused_ws = output->workspaces->items[ws_index];
sway_log(SWAY_DEBUG, "Switched to workspace: %s\n", focused_ws->name);
reset_state:
workspace_scroll_reset(seat, focused_ws);
}
void workspace_scroll_reset(struct sway_seat *seat, struct sway_workspace *ws) {
if (!ws) {
ws = seat_get_focused_workspace(seat);
}
struct sway_output *output = ws->output;
workspace_switch(ws);
seat_consider_warp_to_focus(seat);
// Reset the state
output->workspace_scroll = workspace_scroll_get_default();
output_damage_whole(output);
transaction_commit_dirty();
}

View file

@ -92,6 +92,83 @@ struct decoration_data get_undecorated_decoration_data() {
}; };
} }
// Adjust the box position when switching the workspace
static void adjust_box_to_workspace_offset(struct wlr_box *box,
bool on_focused_workspace, struct sway_output *output) {
float scroll_percent = output->workspace_scroll.percent;
int ws_dimen;
int *box_coord = NULL;
switch (output->workspace_scroll.direction) {
case SWIPE_GESTURE_DIRECTION_NONE:
return;
case SWIPE_GESTURE_DIRECTION_HORIZONTAL:
ws_dimen = output->width;
box_coord = &box->x;
break;
case SWIPE_GESTURE_DIRECTION_VERTICAL:
ws_dimen = output->height;
box_coord = &box->y;
break;
}
if (box_coord == NULL || ws_dimen == 0) {
return;
}
*box_coord -= ws_dimen * scroll_percent;
if (!on_focused_workspace) {
if (scroll_percent > 0) {
*box_coord += ws_dimen;
} else if (scroll_percent < 0) {
*box_coord -= ws_dimen;
}
}
}
// Clips the rendered damage to the workspace region.
// Fixes containers being rendered across workspaces while switching.
static void adjust_damage_to_workspace_bounds(pixman_region32_t *damage,
bool on_focused_workspace, struct sway_output *output) {
float scale = output->wlr_output->scale;
float scroll_percent = output->workspace_scroll.percent;
int x = 0, y = 0;
int ws_dimen = 0;
int *coord = NULL;
switch (output->workspace_scroll.direction) {
case SWIPE_GESTURE_DIRECTION_NONE:
return;
case SWIPE_GESTURE_DIRECTION_HORIZONTAL:
ws_dimen = output->width;
coord = &x;
break;
case SWIPE_GESTURE_DIRECTION_VERTICAL:
ws_dimen = output->height;
coord = &y;
break;
}
if (coord == NULL || ws_dimen == 0) {
return;
}
*coord = round(-ws_dimen * scroll_percent);
if (!on_focused_workspace) {
if (scroll_percent > 0) {
*coord += ws_dimen;
} else if (scroll_percent < 0) {
*coord -= ws_dimen;
}
}
int width, height;
wlr_output_transformed_resolution(output->wlr_output, &width, &height);
pixman_region32_intersect_rect(damage, damage,
0, 0, width, height);
pixman_region32_translate(damage, x * scale, y * scale);
}
/** /**
* Apply scale to a width or height. * Apply scale to a width or height.
* *
@ -208,7 +285,6 @@ void render_blur(struct fx_render_context *ctx, struct wlr_texture *texture,
.src_box = *src_box, .src_box = *src_box,
.dst_box = proj_box, .dst_box = proj_box,
.transform = WL_OUTPUT_TRANSFORM_NORMAL, .transform = WL_OUTPUT_TRANSFORM_NORMAL,
.alpha = &deco_data.alpha,
.clip = &damage, .clip = &damage,
.filter_mode = WLR_SCALE_FILTER_BILINEAR, .filter_mode = WLR_SCALE_FILTER_BILINEAR,
}, },
@ -351,6 +427,12 @@ static void render_surface_iterator(struct sway_output *output,
clip_box.x = fmax(dst_box.x, data->clip_box->x); clip_box.x = fmax(dst_box.x, data->clip_box->x);
clip_box.y = fmax(dst_box.y, data->clip_box->y); clip_box.y = fmax(dst_box.y, data->clip_box->y);
} }
if (data->view) {
adjust_box_to_workspace_offset(&dst_box, data->on_focused_workspace, output);
adjust_box_to_workspace_offset(&clip_box, data->on_focused_workspace, output);
}
scale_box(&dst_box, wlr_output->scale); scale_box(&dst_box, wlr_output->scale);
scale_box(&clip_box, wlr_output->scale); scale_box(&clip_box, wlr_output->scale);
@ -421,6 +503,7 @@ static void render_layer_iterator(struct sway_output *output,
static void render_layer_toplevel(struct fx_render_context *ctx, struct wl_list *layer_surfaces) { static void render_layer_toplevel(struct fx_render_context *ctx, struct wl_list *layer_surfaces) {
struct render_data data = { struct render_data data = {
.deco_data = get_undecorated_decoration_data(), .deco_data = get_undecorated_decoration_data(),
.on_focused_workspace = true,
.ctx = ctx, .ctx = ctx,
}; };
output_layer_for_each_toplevel_surface(ctx->output, layer_surfaces, output_layer_for_each_toplevel_surface(ctx->output, layer_surfaces,
@ -430,6 +513,7 @@ static void render_layer_toplevel(struct fx_render_context *ctx, struct wl_list
static void render_layer_popups(struct fx_render_context *ctx, struct wl_list *layer_surfaces) { static void render_layer_popups(struct fx_render_context *ctx, struct wl_list *layer_surfaces) {
struct render_data data = { struct render_data data = {
.deco_data = get_undecorated_decoration_data(), .deco_data = get_undecorated_decoration_data(),
.on_focused_workspace = true,
.ctx = ctx, .ctx = ctx,
}; };
output_layer_for_each_popup_surface(ctx->output, layer_surfaces, output_layer_for_each_popup_surface(ctx->output, layer_surfaces,
@ -440,6 +524,7 @@ static void render_layer_toplevel(struct fx_render_context *ctx, struct wl_list
static void render_unmanaged(struct fx_render_context *ctx, struct wl_list *unmanaged) { static void render_unmanaged(struct fx_render_context *ctx, struct wl_list *unmanaged) {
struct render_data data = { struct render_data data = {
.deco_data = get_undecorated_decoration_data(), .deco_data = get_undecorated_decoration_data(),
.on_focused_workspace = true,
.ctx = ctx, .ctx = ctx,
}; };
output_unmanaged_for_each_surface(ctx->output, unmanaged, output_unmanaged_for_each_surface(ctx->output, unmanaged,
@ -450,6 +535,7 @@ static void render_unmanaged(struct fx_render_context *ctx, struct wl_list *unma
static void render_drag_icons(struct fx_render_context *ctx, struct wl_list *drag_icons) { static void render_drag_icons(struct fx_render_context *ctx, struct wl_list *drag_icons) {
struct render_data data = { struct render_data data = {
.deco_data = get_undecorated_decoration_data(), .deco_data = get_undecorated_decoration_data(),
.on_focused_workspace = true,
.ctx = ctx, .ctx = ctx,
}; };
output_drag_icons_for_each_surface(ctx->output, drag_icons, output_drag_icons_for_each_surface(ctx->output, drag_icons,
@ -548,9 +634,10 @@ void premultiply_alpha(float color[4], float opacity) {
} }
static void render_view_toplevels(struct fx_render_context *ctx, static void render_view_toplevels(struct fx_render_context *ctx,
struct sway_view *view, struct decoration_data deco_data) { struct sway_view *view, struct decoration_data deco_data, bool on_focused_workspace) {
struct render_data data = { struct render_data data = {
.deco_data = deco_data, .deco_data = deco_data,
.on_focused_workspace = on_focused_workspace,
.view = view, .view = view,
.ctx = ctx, .ctx = ctx,
}; };
@ -598,6 +685,7 @@ static void render_view_popups(struct fx_render_context *ctx, struct sway_view *
struct decoration_data deco_data) { struct decoration_data deco_data) {
struct render_data data = { struct render_data data = {
.deco_data = deco_data, .deco_data = deco_data,
.on_focused_workspace = true,
.view = view, .view = view,
.ctx = ctx, .ctx = ctx,
}; };
@ -700,13 +788,14 @@ static void render_saved_view(struct fx_render_context *ctx, struct sway_view *v
* Render a view's surface, shadow, and left/bottom/right borders. * Render a view's surface, shadow, and left/bottom/right borders.
*/ */
static void render_view(struct fx_render_context *ctx, struct sway_container *con, static void render_view(struct fx_render_context *ctx, struct sway_container *con,
struct border_colors *colors, struct decoration_data deco_data) { struct border_colors *colors, struct decoration_data deco_data,
bool on_focused_workspace) {
struct sway_view *view = con->view; struct sway_view *view = con->view;
if (!wl_list_empty(&view->saved_buffers)) { if (!wl_list_empty(&view->saved_buffers)) {
render_saved_view(ctx, view, deco_data); render_saved_view(ctx, view, deco_data);
} else if (view->surface) { } else if (view->surface) {
render_view_toplevels(ctx, view, deco_data); render_view_toplevels(ctx, view, deco_data, on_focused_workspace);
} }
struct sway_container_state *state = &con->current; struct sway_container_state *state = &con->current;
@ -725,6 +814,7 @@ static void render_view(struct fx_render_context *ctx, struct sway_container *co
box.y = floor(state->y) - ctx->output->ly; box.y = floor(state->y) - ctx->output->ly;
box.width = state->width; box.width = state->width;
box.height = state->height; box.height = state->height;
adjust_box_to_workspace_offset(&box, on_focused_workspace, ctx->output);
scale_box(&box, output_scale); scale_box(&box, output_scale);
int shadow_corner_radius = corner_radius == 0 ? 0 : corner_radius + state->border_thickness; int shadow_corner_radius = corner_radius == 0 ? 0 : corner_radius + state->border_thickness;
float* shadow_color = view_is_urgent(view) || state->focused ? float* shadow_color = view_is_urgent(view) || state->focused ?
@ -748,6 +838,7 @@ static void render_view(struct fx_render_context *ctx, struct sway_container *co
box.y = floor(state->content_y); box.y = floor(state->content_y);
box.width = state->border_thickness; box.width = state->border_thickness;
box.height = state->content_height - corner_radius; box.height = state->content_height - corner_radius;
adjust_box_to_workspace_offset(&box, on_focused_workspace, ctx->output);
if (corner_radius && !deco_data.has_titlebar) { if (corner_radius && !deco_data.has_titlebar) {
box.y += corner_radius; box.y += corner_radius;
box.height -= corner_radius; box.height -= corner_radius;
@ -771,6 +862,7 @@ static void render_view(struct fx_render_context *ctx, struct sway_container *co
box.y = floor(state->content_y); box.y = floor(state->content_y);
box.width = state->border_thickness; box.width = state->border_thickness;
box.height = state->content_height - corner_radius; box.height = state->content_height - corner_radius;
adjust_box_to_workspace_offset(&box, on_focused_workspace, ctx->output);
if (corner_radius && !deco_data.has_titlebar) { if (corner_radius && !deco_data.has_titlebar) {
box.y += corner_radius; box.y += corner_radius;
box.height -= corner_radius; box.height -= corner_radius;
@ -790,6 +882,8 @@ static void render_view(struct fx_render_context *ctx, struct sway_container *co
box.y = floor(state->content_y + state->content_height); box.y = floor(state->content_y + state->content_height);
box.width = state->width; box.width = state->width;
box.height = state->border_thickness; box.height = state->border_thickness;
adjust_box_to_workspace_offset(&box, on_focused_workspace, ctx->output);
// adjust sizing for rounded border corners // adjust sizing for rounded border corners
if (deco_data.corner_radius) { if (deco_data.corner_radius) {
box.x += corner_radius + state->border_thickness; box.x += corner_radius + state->border_thickness;
@ -805,6 +899,7 @@ static void render_view(struct fx_render_context *ctx, struct sway_container *co
if (state->border_left) { if (state->border_left) {
box.x = floor(state->x); box.x = floor(state->x);
box.y = floor(state->y + state->height - size); box.y = floor(state->y + state->height - size);
adjust_box_to_workspace_offset(&box, on_focused_workspace, ctx->output);
box.width = size; box.width = size;
box.height = size; box.height = size;
scale_box(&box, output_scale); scale_box(&box, output_scale);
@ -814,8 +909,7 @@ static void render_view(struct fx_render_context *ctx, struct sway_container *co
if (state->border_right) { if (state->border_right) {
box.x = floor(state->x + state->width - size); box.x = floor(state->x + state->width - size);
box.y = floor(state->y + state->height - size); box.y = floor(state->y + state->height - size);
box.width = size; adjust_box_to_workspace_offset(&box, on_focused_workspace, ctx->output);
box.height = size;
scale_box(&box, output_scale); scale_box(&box, output_scale);
render_rounded_border_corner(ctx, &box, color, scaled_corner_radius, render_rounded_border_corner(ctx, &box, color, scaled_corner_radius,
scaled_border_thickness, BOTTOM_RIGHT); scaled_border_thickness, BOTTOM_RIGHT);
@ -836,7 +930,7 @@ static void render_view(struct fx_render_context *ctx, struct sway_container *co
static void render_titlebar(struct fx_render_context *ctx, struct sway_container *con, static void render_titlebar(struct fx_render_context *ctx, struct sway_container *con,
int x, int y, int width, struct border_colors *colors, int corner_radius, int x, int y, int width, struct border_colors *colors, int corner_radius,
enum corner_location corner_location, struct wlr_texture *title_texture, enum corner_location corner_location, struct wlr_texture *title_texture,
struct wlr_texture *marks_texture) { struct wlr_texture *marks_texture, bool on_focused_workspace) {
struct wlr_box box; struct wlr_box box;
float color[4]; float color[4];
struct sway_output *output = ctx->output; struct sway_output *output = ctx->output;
@ -868,6 +962,7 @@ static void render_titlebar(struct fx_render_context *ctx, struct sway_container
box.width -= corner_radius; box.width -= corner_radius;
} }
} }
adjust_box_to_workspace_offset(&box, on_focused_workspace, output);
scale_box(&box, output_scale); scale_box(&box, output_scale);
render_rect(ctx, &box, color); render_rect(ctx, &box, color);
@ -877,6 +972,7 @@ static void render_titlebar(struct fx_render_context *ctx, struct sway_container
box.y = y + container_titlebar_height() - titlebar_border_thickness; box.y = y + container_titlebar_height() - titlebar_border_thickness;
box.width = width; box.width = width;
box.height = titlebar_border_thickness; box.height = titlebar_border_thickness;
adjust_box_to_workspace_offset(&box, on_focused_workspace, output);
scale_box(&box, output_scale); scale_box(&box, output_scale);
render_rect(ctx, &box, color); render_rect(ctx, &box, color);
} }
@ -890,6 +986,7 @@ static void render_titlebar(struct fx_render_context *ctx, struct sway_container
box.height -= corner_radius; box.height -= corner_radius;
box.y += corner_radius; box.y += corner_radius;
} }
adjust_box_to_workspace_offset(&box, on_focused_workspace, output);
scale_box(&box, output_scale); scale_box(&box, output_scale);
render_rect(ctx, &box, color); render_rect(ctx, &box, color);
@ -902,6 +999,7 @@ static void render_titlebar(struct fx_render_context *ctx, struct sway_container
box.height -= corner_radius; box.height -= corner_radius;
box.y += corner_radius; box.y += corner_radius;
} }
adjust_box_to_workspace_offset(&box, on_focused_workspace, output);
scale_box(&box, output_scale); scale_box(&box, output_scale);
render_rect(ctx, &box, color); render_rect(ctx, &box, color);
@ -913,6 +1011,7 @@ static void render_titlebar(struct fx_render_context *ctx, struct sway_container
box.y = y; box.y = y;
box.width = corner_radius * 2; box.width = corner_radius * 2;
box.height = corner_radius * 2; box.height = corner_radius * 2;
adjust_box_to_workspace_offset(&box, on_focused_workspace, output);
scale_box(&box, output_scale); scale_box(&box, output_scale);
render_rounded_border_corner(ctx, &box, color, corner_radius, render_rounded_border_corner(ctx, &box, color, corner_radius,
titlebar_border_thickness * output_scale, TOP_LEFT); titlebar_border_thickness * output_scale, TOP_LEFT);
@ -924,6 +1023,7 @@ static void render_titlebar(struct fx_render_context *ctx, struct sway_container
box.y = y; box.y = y;
box.width = corner_radius * 2; box.width = corner_radius * 2;
box.height = corner_radius * 2; box.height = corner_radius * 2;
adjust_box_to_workspace_offset(&box, on_focused_workspace, output);
scale_box(&box, output_scale); scale_box(&box, output_scale);
render_rounded_border_corner(ctx, &box, color, corner_radius, render_rounded_border_corner(ctx, &box, color, corner_radius,
titlebar_border_thickness * output_scale, TOP_RIGHT); titlebar_border_thickness * output_scale, TOP_RIGHT);
@ -977,6 +1077,7 @@ static void render_titlebar(struct fx_render_context *ctx, struct sway_container
if (ob_inner_width < clip_box.width) { if (ob_inner_width < clip_box.width) {
clip_box.width = ob_inner_width; clip_box.width = ob_inner_width;
} }
adjust_box_to_workspace_offset(&texture_box, on_focused_workspace, output);
render_texture(ctx, marks_texture, render_texture(ctx, marks_texture,
NULL, &texture_box, &clip_box, WL_OUTPUT_TRANSFORM_NORMAL, deco_data); NULL, &texture_box, &clip_box, WL_OUTPUT_TRANSFORM_NORMAL, deco_data);
@ -987,11 +1088,13 @@ static void render_titlebar(struct fx_render_context *ctx, struct sway_container
box.y = roundf((y + titlebar_border_thickness) * output_scale); box.y = roundf((y + titlebar_border_thickness) * output_scale);
box.width = clip_box.width; box.width = clip_box.width;
box.height = ob_padding_above; box.height = ob_padding_above;
adjust_box_to_workspace_offset(&box, on_focused_workspace, output);
render_rect(ctx, &box, color); render_rect(ctx, &box, color);
// Padding below // Padding below
box.y += ob_padding_above + clip_box.height; box.y += ob_padding_above + clip_box.height;
box.height = ob_padding_below + bottom_border_compensation; box.height = ob_padding_below + bottom_border_compensation;
adjust_box_to_workspace_offset(&box, on_focused_workspace, output);
render_rect(ctx, &box, color); render_rect(ctx, &box, color);
} }
@ -1049,6 +1152,8 @@ static void render_titlebar(struct fx_render_context *ctx, struct sway_container
clip_box.width = ob_inner_width - ob_marks_width; clip_box.width = ob_inner_width - ob_marks_width;
} }
adjust_box_to_workspace_offset(&texture_box, on_focused_workspace, output);
adjust_box_to_workspace_offset(&clip_box, on_focused_workspace, output);
render_texture(ctx, title_texture, render_texture(ctx, title_texture,
NULL, &texture_box, &clip_box, WL_OUTPUT_TRANSFORM_NORMAL, deco_data); NULL, &texture_box, &clip_box, WL_OUTPUT_TRANSFORM_NORMAL, deco_data);
@ -1059,6 +1164,7 @@ static void render_titlebar(struct fx_render_context *ctx, struct sway_container
box.y = roundf((y + titlebar_border_thickness) * output_scale); box.y = roundf((y + titlebar_border_thickness) * output_scale);
box.width = clip_box.width; box.width = clip_box.width;
box.height = ob_padding_above; box.height = ob_padding_above;
// adjust_box_to_workspace_offset(&box, on_focused_workspace, output);
render_rect(ctx, &box, color); render_rect(ctx, &box, color);
// Padding below // Padding below
@ -1098,6 +1204,7 @@ static void render_titlebar(struct fx_render_context *ctx, struct sway_container
box.x = ob_left_x + ob_left_width + round(output_x * output_scale); box.x = ob_left_x + ob_left_width + round(output_x * output_scale);
box.y = roundf(bg_y * output_scale); box.y = roundf(bg_y * output_scale);
box.height = ob_bg_height + bottom_border_compensation; box.height = ob_bg_height + bottom_border_compensation;
adjust_box_to_workspace_offset(&box, on_focused_workspace, output);
render_rect(ctx, &box, color); render_rect(ctx, &box, color);
} }
@ -1112,6 +1219,7 @@ static void render_titlebar(struct fx_render_context *ctx, struct sway_container
if (box.x + box.width < left_x) { if (box.x + box.width < left_x) {
box.width += left_x - box.x - box.width; box.width += left_x - box.x - box.width;
} }
adjust_box_to_workspace_offset(&box, on_focused_workspace, output);
if (corner_radius && (corner_location == TOP_LEFT || corner_location == ALL)) { if (corner_radius && (corner_location == TOP_LEFT || corner_location == ALL)) {
render_rounded_rect(ctx, &box, color, corner_radius, TOP_LEFT); render_rounded_rect(ctx, &box, color, corner_radius, TOP_LEFT);
} else { } else {
@ -1130,6 +1238,7 @@ static void render_titlebar(struct fx_render_context *ctx, struct sway_container
box.width += box.x - right_rx; box.width += box.x - right_rx;
box.x = right_rx; box.x = right_rx;
} }
adjust_box_to_workspace_offset(&box, on_focused_workspace, output);
if (corner_radius && (corner_location == TOP_RIGHT || corner_location == ALL)) { if (corner_radius && (corner_location == TOP_RIGHT || corner_location == ALL)) {
render_rounded_rect(ctx, &box, color, corner_radius, TOP_RIGHT); render_rounded_rect(ctx, &box, color, corner_radius, TOP_RIGHT);
} else { } else {
@ -1141,7 +1250,7 @@ static void render_titlebar(struct fx_render_context *ctx, struct sway_container
* Render the top border line for a view using "border pixel". * Render the top border line for a view using "border pixel".
*/ */
static void render_top_border(struct fx_render_context *ctx, struct sway_container *con, static void render_top_border(struct fx_render_context *ctx, struct sway_container *con,
struct border_colors *colors, int corner_radius) { struct border_colors *colors, int corner_radius, bool on_focused_workspace) {
struct sway_container_state *state = &con->current; struct sway_container_state *state = &con->current;
if (!state->border_top) { if (!state->border_top) {
return; return;
@ -1157,6 +1266,7 @@ static void render_top_border(struct fx_render_context *ctx, struct sway_contain
box.y = floor(state->y); box.y = floor(state->y);
box.width = state->width - (2 * corner_radius); box.width = state->width - (2 * corner_radius);
box.height = state->border_thickness; box.height = state->border_thickness;
adjust_box_to_workspace_offset(&box, on_focused_workspace, ctx->output);
scale_box(&box, output_scale); scale_box(&box, output_scale);
render_rect(ctx, &box, color); render_rect(ctx, &box, color);
@ -1167,6 +1277,7 @@ static void render_top_border(struct fx_render_context *ctx, struct sway_contain
if (state->border_left) { if (state->border_left) {
box.x = floor(state->x); box.x = floor(state->x);
box.y = floor(state->y); box.y = floor(state->y);
adjust_box_to_workspace_offset(&box, on_focused_workspace, ctx->output);
box.width = size; box.width = size;
box.height = size; box.height = size;
scale_box(&box, output_scale); scale_box(&box, output_scale);
@ -1176,6 +1287,7 @@ static void render_top_border(struct fx_render_context *ctx, struct sway_contain
if (state->border_right) { if (state->border_right) {
box.x = floor(state->x + state->width - size); box.x = floor(state->x + state->width - size);
box.y = floor(state->y); box.y = floor(state->y);
adjust_box_to_workspace_offset(&box, on_focused_workspace, ctx->output);
box.width = size; box.width = size;
box.height = size; box.height = size;
scale_box(&box, output_scale); scale_box(&box, output_scale);
@ -1191,10 +1303,13 @@ struct parent_data {
list_t *children; list_t *children;
bool focused; bool focused;
struct sway_container *active_child; struct sway_container *active_child;
// Indicates whether the target is on the focused workspace or not.
bool on_focused_workspace;
}; };
static void render_container(struct fx_render_context *ctx, static void render_container(struct fx_render_context *ctx,
struct sway_container *con, bool parent_focused); struct sway_container *con, bool parent_focused, bool is_current_ws);
// TODO: no rounding top corners when rendering with titlebar // TODO: no rounding top corners when rendering with titlebar
/** /**
@ -1254,17 +1369,18 @@ static void render_containers_linear(struct fx_render_context *ctx, struct paren
.discard_transparent = false, .discard_transparent = false,
.shadow = child->shadow_enabled, .shadow = child->shadow_enabled,
}; };
render_view(ctx, child, colors, deco_data); render_view(ctx, child, colors, deco_data, parent->on_focused_workspace);
if (has_titlebar) { if (has_titlebar) {
render_titlebar(ctx, child, floor(state->x), floor(state->y), render_titlebar(ctx, child, floor(state->x), floor(state->y),
state->width, colors, deco_data.corner_radius, state->width, colors, deco_data.corner_radius,
ALL, title_texture, marks_texture); ALL, title_texture, marks_texture, parent->on_focused_workspace);
} else if (state->border == B_PIXEL) { } else if (state->border == B_PIXEL) {
render_top_border(ctx, child, colors, deco_data.corner_radius); render_top_border(ctx, child, colors, deco_data.corner_radius, parent->on_focused_workspace);
} }
} else { } else {
render_container(ctx, child, render_container(ctx, child,
parent->focused || child->current.focused); parent->focused || child->current.focused,
parent->on_focused_workspace);
} }
} }
} }
@ -1359,7 +1475,8 @@ static void render_containers_tabbed(struct fx_render_context *ctx, struct paren
} }
render_titlebar(ctx, child, x, parent->box.y, tab_width, colors, render_titlebar(ctx, child, x, parent->box.y, tab_width, colors,
corner_radius, corner_location, title_texture, marks_texture); corner_radius, corner_location, title_texture, marks_texture,
parent->on_focused_workspace);
if (child == current) { if (child == current) {
current_colors = colors; current_colors = colors;
@ -1368,10 +1485,12 @@ static void render_containers_tabbed(struct fx_render_context *ctx, struct paren
// Render surface and left/right/bottom borders // Render surface and left/right/bottom borders
if (current->view) { if (current->view) {
render_view(ctx, current, current_colors, deco_data); render_view(ctx, current, current_colors, deco_data,
parent->on_focused_workspace);
} else { } else {
render_container(ctx, current, render_container(ctx, current,
parent->focused || current->current.focused); parent->focused || current->current.focused,
parent->on_focused_workspace);
} }
} }
@ -1438,7 +1557,7 @@ static void render_containers_stacked(struct fx_render_context *ctx, struct pare
int y = parent->box.y + titlebar_height * i; int y = parent->box.y + titlebar_height * i;
int corner_radius = i != 0 ? 0 : deco_data.corner_radius; int corner_radius = i != 0 ? 0 : deco_data.corner_radius;
render_titlebar(ctx, child, parent->box.x, y, parent->box.width, colors, render_titlebar(ctx, child, parent->box.x, y, parent->box.width, colors,
corner_radius, ALL, title_texture, marks_texture); corner_radius, ALL, title_texture, marks_texture, parent->on_focused_workspace);
if (child == current) { if (child == current) {
current_colors = colors; current_colors = colors;
@ -1447,10 +1566,12 @@ static void render_containers_stacked(struct fx_render_context *ctx, struct pare
// Render surface and left/right/bottom borders // Render surface and left/right/bottom borders
if (current->view) { if (current->view) {
render_view(ctx, current, current_colors, deco_data); render_view(ctx, current, current_colors, deco_data,
parent->on_focused_workspace);
} else { } else {
render_container(ctx, current, render_container(ctx, current,
parent->focused || current->current.focused); parent->focused || current->current.focused,
parent->on_focused_workspace);
} }
} }
@ -1479,7 +1600,7 @@ static void render_containers(struct fx_render_context *ctx, struct parent_data
} }
static void render_container(struct fx_render_context *ctx, static void render_container(struct fx_render_context *ctx,
struct sway_container *con, bool focused) { struct sway_container *con, bool focused, bool is_focused_ws) {
struct parent_data data = { struct parent_data data = {
.layout = con->current.layout, .layout = con->current.layout,
.box = { .box = {
@ -1491,12 +1612,13 @@ static void render_container(struct fx_render_context *ctx,
.children = con->current.children, .children = con->current.children,
.focused = focused, .focused = focused,
.active_child = con->current.focused_inactive_child, .active_child = con->current.focused_inactive_child,
.on_focused_workspace = is_focused_ws,
}; };
render_containers(ctx, &data); render_containers(ctx, &data);
} }
static void render_workspace(struct fx_render_context *ctx, static void render_workspace(struct fx_render_context *ctx,
struct sway_workspace *ws, bool focused) { struct sway_workspace *ws, bool focused, bool on_focused_workspace) {
struct parent_data data = { struct parent_data data = {
.layout = ws->current.layout, .layout = ws->current.layout,
.box = { .box = {
@ -1508,12 +1630,13 @@ static void render_workspace(struct fx_render_context *ctx,
.children = ws->current.tiling, .children = ws->current.tiling,
.focused = focused, .focused = focused,
.active_child = ws->current.focused_inactive_child, .active_child = ws->current.focused_inactive_child,
.on_focused_workspace = on_focused_workspace,
}; };
render_containers(ctx, &data); render_containers(ctx, &data);
} }
static void render_floating_container(struct fx_render_context *ctx, static void render_floating_container(struct fx_render_context *ctx,
struct sway_container *con) { struct sway_container *con, bool is_focused_ws) {
struct sway_container_state *state = &con->current; struct sway_container_state *state = &con->current;
if (con->view) { if (con->view) {
struct sway_view *view = con->view; struct sway_view *view = con->view;
@ -1549,32 +1672,44 @@ static void render_floating_container(struct fx_render_context *ctx,
.discard_transparent = false, .discard_transparent = false,
.shadow = con->shadow_enabled, .shadow = con->shadow_enabled,
}; };
render_view(ctx, con, colors, deco_data); render_view(ctx, con, colors, deco_data, is_focused_ws);
if (has_titlebar) { if (has_titlebar) {
render_titlebar(ctx, con, floor(con->current.x), floor(con->current.y), con->current.width, render_titlebar(ctx, con, floor(con->current.x), floor(con->current.y), con->current.width,
colors, deco_data.corner_radius, ALL, title_texture, marks_texture); colors, deco_data.corner_radius, ALL, title_texture, marks_texture, is_focused_ws);
} else if (state->border == B_PIXEL) { } else if (state->border == B_PIXEL) {
render_top_border(ctx, con, colors, deco_data.corner_radius); render_top_border(ctx, con, colors, deco_data.corner_radius, is_focused_ws);
} }
} else { } else {
render_container(ctx, con, state->focused); render_container(ctx, con, state->focused, is_focused_ws);
} }
} }
static void render_floating(struct fx_render_context *ctx) { static void render_floating(struct fx_render_context *ctx,
struct sway_workspace *other_ws, bool has_fullscreen) {
for (int i = 0; i < root->outputs->length; ++i) { for (int i = 0; i < root->outputs->length; ++i) {
struct sway_output *output = root->outputs->items[i]; struct sway_output *output = root->outputs->items[i];
for (int j = 0; j < output->current.workspaces->length; ++j) {
struct sway_workspace *ws = output->current.workspaces->items[j]; // Don't render floating windows across outputs when switching workspaces
if (!workspace_is_visible(ws)) { if (output->workspace_scroll.percent != 0 && output != ctx->output) {
continue; continue;
} }
struct sway_workspace *visible_ws = output->current.active_workspace;
for (int j = 0; j < output->current.workspaces->length; ++j) {
struct sway_workspace *ws = output->current.workspaces->items[j];
// Only render affected workspaces
if ((ws != other_ws && ws != visible_ws) ||
(workspace_is_visible(ws) && has_fullscreen)) {
continue;
}
for (int k = 0; k < ws->current.floating->length; ++k) { for (int k = 0; k < ws->current.floating->length; ++k) {
struct sway_container *floater = ws->current.floating->items[k]; struct sway_container *floater = ws->current.floating->items[k];
if (floater->current.fullscreen_mode != FULLSCREEN_NONE) { if (floater->current.fullscreen_mode != FULLSCREEN_NONE) {
continue; continue;
} }
render_floating_container(ctx, floater); render_floating_container(ctx, floater, ws == visible_ws);
} }
} }
} }
@ -1587,6 +1722,57 @@ static void render_seatops(struct fx_render_context *ctx) {
} }
} }
static void render_fullscreen_con(struct fx_render_context *ctx,
pixman_region32_t *transformed_damage, struct sway_container *fullscreen_con,
struct sway_workspace *workspace, bool clear_whole_screen) {
struct wlr_output *wlr_output = ctx->output->wlr_output;
bool on_focused_workspace = workspace == ctx->output->current.active_workspace;
// Only clear the transformed fullscreen bounds
pixman_region32_t dmg;
pixman_region32_init(&dmg);
if (!clear_whole_screen) {
pixman_region32_copy(&dmg, transformed_damage);
adjust_damage_to_workspace_bounds(&dmg, on_focused_workspace, ctx->output);
transformed_damage = &dmg;
}
fx_render_pass_add_rect(ctx->pass, &(struct fx_render_rect_options){
.base = {
.box = { .width = wlr_output->width, .height = wlr_output->height },
.color = { .r = 0, .g = 0, .b = 0, .a = 1 },
.clip = transformed_damage,
},
});
if (fullscreen_con->view) {
struct decoration_data deco_data = get_undecorated_decoration_data();
deco_data.saturation = fullscreen_con->saturation;
if (!wl_list_empty(&fullscreen_con->view->saved_buffers)) {
render_saved_view(ctx, fullscreen_con->view, deco_data);
} else if (fullscreen_con->view->surface) {
render_view_toplevels(ctx, fullscreen_con->view, deco_data, on_focused_workspace);
}
} else {
render_container(ctx, fullscreen_con, fullscreen_con->current.focused, on_focused_workspace);
}
for (int i = 0; i < workspace->current.floating->length; ++i) {
struct sway_container *floater =
workspace->current.floating->items[i];
if (container_is_transient_for(floater, fullscreen_con)) {
render_floating_container(ctx, floater, on_focused_workspace);
}
}
#if HAVE_XWAYLAND
render_unmanaged(ctx, &root->xwayland_unmanaged);
#endif
pixman_region32_fini(&dmg);
}
void output_render(struct fx_render_context *ctx) { void output_render(struct fx_render_context *ctx) {
struct wlr_output *wlr_output = ctx->output->wlr_output; struct wlr_output *wlr_output = ctx->output->wlr_output;
struct sway_output *output = ctx->output; struct sway_output *output = ctx->output;
@ -1599,10 +1785,29 @@ void output_render(struct fx_render_context *ctx) {
return; return;
} }
// Get the sibling workspaces
struct sway_workspace *other_ws = NULL;
if (output->workspace_scroll.percent < 0) {
other_ws = workspace_output_prev(workspace,
config->workspace_gesture_wrap_around);
} else if (output->workspace_scroll.percent > 0) {
other_ws = workspace_output_next(workspace,
config->workspace_gesture_wrap_around);
}
struct sway_container *fullscreen_con = root->fullscreen_global; struct sway_container *fullscreen_con = root->fullscreen_global;
if (!fullscreen_con) { if (!fullscreen_con) {
fullscreen_con = workspace->current.fullscreen; fullscreen_con = workspace->current.fullscreen;
} }
// Also check if the sibling workspace has a fullscreen container
bool has_fullscreen = fullscreen_con != NULL;
bool other_ws_has_fullscreen = false;
if (other_ws) {
other_ws_has_fullscreen = other_ws->current.fullscreen;
if (!has_fullscreen) {
has_fullscreen = other_ws_has_fullscreen;
}
}
if (!pixman_region32_not_empty(damage)) { if (!pixman_region32_not_empty(damage)) {
// Output isn't damaged but needs buffer swap // Output isn't damaged but needs buffer swap
@ -1643,6 +1848,7 @@ void output_render(struct fx_render_context *ctx) {
if (server.session_lock.lock != NULL) { if (server.session_lock.lock != NULL) {
struct render_data data = { struct render_data data = {
.deco_data = get_undecorated_decoration_data(), .deco_data = get_undecorated_decoration_data(),
.on_focused_workspace = true,
.ctx = ctx, .ctx = ctx,
}; };
@ -1666,37 +1872,9 @@ void output_render(struct fx_render_context *ctx) {
goto render_overlay; goto render_overlay;
} }
if (fullscreen_con) { if (fullscreen_con && output->workspace_scroll.percent == 0) {
fx_render_pass_add_rect(ctx->pass, &(struct fx_render_rect_options){ // Only draw fullscreen con if not transitioning between workspaces
.base = { render_fullscreen_con(ctx, &transformed_damage, fullscreen_con, workspace, true);
.box = { .width = wlr_output->width, .height = wlr_output->height },
.color = { .r = 0, .g = 0, .b = 0, .a = 1 },
.clip = &transformed_damage,
},
});
if (fullscreen_con->view) {
struct decoration_data deco_data = get_undecorated_decoration_data();
deco_data.saturation = fullscreen_con->saturation;
if (!wl_list_empty(&fullscreen_con->view->saved_buffers)) {
render_saved_view(ctx, fullscreen_con->view, deco_data);
} else if (fullscreen_con->view->surface) {
render_view_toplevels(ctx, fullscreen_con->view, deco_data);
}
} else {
render_container(ctx, fullscreen_con, fullscreen_con->current.focused);
}
for (int i = 0; i < workspace->current.floating->length; ++i) {
struct sway_container *floater =
workspace->current.floating->items[i];
if (container_is_transient_for(floater, fullscreen_con)) {
render_floating_container(ctx, floater);
}
}
#if HAVE_XWAYLAND
render_unmanaged(ctx, &root->xwayland_unmanaged);
#endif
} else { } else {
int output_width, output_height; int output_width, output_height;
wlr_output_transformed_resolution(wlr_output, &output_width, &output_height); wlr_output_transformed_resolution(wlr_output, &output_width, &output_height);
@ -1779,8 +1957,16 @@ void output_render(struct fx_render_context *ctx) {
fx_render_pass_add_optimized_blur(ctx->pass, &blur_options); fx_render_pass_add_optimized_blur(ctx->pass, &blur_options);
} }
render_workspace(ctx, workspace, workspace->current.focused); // Render both workspaces
render_floating(ctx); if (!other_ws_has_fullscreen && other_ws) {
render_workspace(ctx, other_ws, false, false);
}
if (!fullscreen_con) {
render_workspace(ctx, workspace, workspace->current.focused, true);
}
render_floating(ctx,
!other_ws_has_fullscreen ? other_ws : NULL,
fullscreen_con != NULL);
#if HAVE_XWAYLAND #if HAVE_XWAYLAND
render_unmanaged(ctx, &root->xwayland_unmanaged); render_unmanaged(ctx, &root->xwayland_unmanaged);
#endif #endif
@ -1793,6 +1979,43 @@ void output_render(struct fx_render_context *ctx) {
&output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM]); &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM]);
render_layer_popups(ctx, render_layer_popups(ctx,
&output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP]); &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP]);
// Render the fullscreen containers on top
if (has_fullscreen) {
float clear_color[] = {0.25f, 0.25f, 0.25f, 1.0f};
// Render a blank rect next to the fullscreen container if
// there's no sibling workspace in the swipes direction
if (!other_ws) {
struct wlr_box mon_box = { 0, 0, output->width, output->height };
adjust_box_to_workspace_offset(&mon_box, false, output);
scale_box(&mon_box, output->wlr_output->scale);
render_rect(ctx, &mon_box, clear_color);
// Render a shadow to separate the edge and the fullscreen
// container
if (config_should_parameters_shadow()) {
struct wlr_box shadow_box = { 0, 0, output->width, output->height };
adjust_box_to_workspace_offset(&shadow_box, true, output);
scale_box(&shadow_box, output->wlr_output->scale);
// Render rect to fix minor pixel gaps between fullscreen
// container and shadow
render_rect(ctx, &shadow_box, clear_color);
render_box_shadow(ctx, &shadow_box,
config->shadow_color, config->shadow_blur_sigma, 0,
0, 0);
}
} else {
// Render sibling fullscreen container
struct sway_container *f_con = other_ws->current.fullscreen;
if (other_ws->current.fullscreen) {
render_fullscreen_con(ctx, &transformed_damage, f_con, other_ws, false);
}
}
// Render focused fullscreen container
if (fullscreen_con) {
render_fullscreen_con(ctx, &transformed_damage, fullscreen_con, workspace, false);
}
}
} }
render_seatops(ctx); render_seatops(ctx);

View file

@ -0,0 +1,12 @@
#include <math.h>
#include "sway/fx_util/animation_utils.h"
double lerp (double a, double b, double t) {
return a * (1.0 - t) + b * t;
}
double ease_out_cubic (double t) {
double p = t - 1;
return pow(p, 3) + 1;
}

View file

@ -1053,6 +1053,14 @@ static void handle_swipe_begin(struct sway_seat *seat,
if (gesture_binding_check(bindings, GESTURE_TYPE_SWIPE, event->fingers, device)) { if (gesture_binding_check(bindings, GESTURE_TYPE_SWIPE, event->fingers, device)) {
struct seatop_default_event *seatop = seat->seatop_data; struct seatop_default_event *seatop = seat->seatop_data;
gesture_tracker_begin(&seatop->gestures, GESTURE_TYPE_SWIPE, event->fingers); gesture_tracker_begin(&seatop->gestures, GESTURE_TYPE_SWIPE, event->fingers);
} 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_HORIZONTAL, event->fingers);
workspace_scroll_begin(seat, SWIPE_GESTURE_DIRECTION_HORIZONTAL);
} 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);
workspace_scroll_begin(seat, SWIPE_GESTURE_DIRECTION_VERTICAL);
} else { } else {
// ... otherwise forward to client // ... otherwise forward to client
struct sway_cursor *cursor = seat->cursor; struct sway_cursor *cursor = seat->cursor;
@ -1070,6 +1078,29 @@ static void handle_swipe_update(struct sway_seat *seat,
if (gesture_tracker_check(&seatop->gestures, GESTURE_TYPE_SWIPE)) { if (gesture_tracker_check(&seatop->gestures, GESTURE_TYPE_SWIPE)) {
gesture_tracker_update(&seatop->gestures, gesture_tracker_update(&seatop->gestures,
event->dx, event->dy, NAN, NAN); event->dx, event->dy, NAN, NAN);
} else if (gesture_tracker_check(&seatop->gestures, GESTURE_TYPE_WORKSPACE_SWIPE_HORIZONTAL) ||
gesture_tracker_check(&seatop->gestures, GESTURE_TYPE_WORKSPACE_SWIPE_VERTICAL)) {
// 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;
// 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,
.directions = GESTURE_DIRECTION_NONE,
};
struct sway_gesture_binding *binding = gesture_binding_match(
config->current_mode->gesture_bindings, &gesture, input);
if (binding) {
int invert = binding->flags & BINDING_INVERTED ? 1 : -1;
workspace_scroll_update(seat, tracker, event, invert);
}
} else { } else {
// ... otherwise forward to client // ... otherwise forward to client
struct sway_cursor *cursor = seat->cursor; struct sway_cursor *cursor = seat->cursor;
@ -1083,13 +1114,17 @@ static void handle_swipe_end(struct sway_seat *seat,
struct wlr_pointer_swipe_end_event *event) { struct wlr_pointer_swipe_end_event *event) {
// Ensure gesture is being tracked and was not cancelled // Ensure gesture is being tracked and was not cancelled
struct seatop_default_event *seatop = seat->seatop_data; 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_HORIZONTAL) &&
!gesture_tracker_check(&seatop->gestures, GESTURE_TYPE_WORKSPACE_SWIPE_VERTICAL)) {
struct sway_cursor *cursor = seat->cursor; struct sway_cursor *cursor = seat->cursor;
wlr_pointer_gestures_v1_send_swipe_end(server.input->pointer_gestures, wlr_pointer_gestures_v1_send_swipe_end(server.input->pointer_gestures,
cursor->seat->wlr_seat, event->time_msec, event->cancelled); cursor->seat->wlr_seat, event->time_msec, event->cancelled);
return; return;
} }
if (event->cancelled) { if (event->cancelled &&
seatop->gestures.type != GESTURE_TYPE_WORKSPACE_SWIPE_HORIZONTAL
&& seatop->gestures.type != GESTURE_TYPE_WORKSPACE_SWIPE_VERTICAL) {
gesture_tracker_cancel(&seatop->gestures); gesture_tracker_cancel(&seatop->gestures);
return; return;
} }
@ -1101,7 +1136,15 @@ static void handle_swipe_end(struct sway_seat *seat,
&seatop->gestures, device); &seatop->gestures, device);
if (binding) { if (binding) {
switch (binding->gesture.type) {
case GESTURE_TYPE_WORKSPACE_SWIPE_HORIZONTAL:
case GESTURE_TYPE_WORKSPACE_SWIPE_VERTICAL:
workspace_scroll_end(seat);
break;
default:
gesture_binding_execute(seat, binding); gesture_binding_execute(seat, binding);
break;
}
} }
} }
@ -1117,6 +1160,23 @@ static void handle_rebase(struct sway_seat *seat, uint32_t time_msec) {
e->previous_node = node_at_coords(seat, e->previous_node = node_at_coords(seat,
cursor->cursor->x, cursor->cursor->y, &surface, &sx, &sy); cursor->cursor->x, cursor->cursor->y, &surface, &sx, &sy);
// Reset the swipe if any other button is pressed during the swipe
struct sway_workspace *focused_ws = seat_get_focused_workspace(seat);
if (focused_ws) {
struct sway_output *output = focused_ws->output;
struct workspace_scroll workspace_scroll_default = workspace_scroll_get_default();
switch (e->gestures.type) {
default:
break;
case GESTURE_TYPE_WORKSPACE_SWIPE_HORIZONTAL:
case GESTURE_TYPE_WORKSPACE_SWIPE_VERTICAL:
if (!workspace_scroll_equal(&output->workspace_scroll, &workspace_scroll_default)) {
workspace_scroll_reset(seat, NULL);
}
break;
}
}
if (surface) { if (surface) {
if (seat_is_input_allowed(seat, surface)) { if (seat_is_input_allowed(seat, surface)) {
wlr_seat_pointer_notify_enter(seat->wlr_seat, surface, sx, sy); wlr_seat_pointer_notify_enter(seat->wlr_seat, surface, sx, sy);

View file

@ -24,6 +24,8 @@ sway_sources = files(
'desktop/xdg_shell.c', 'desktop/xdg_shell.c',
'desktop/launcher.c', 'desktop/launcher.c',
'fx_util/animation_utils.c',
'input/input-manager.c', 'input/input-manager.c',
'input/cursor.c', 'input/cursor.c',
'input/keyboard.c', 'input/keyboard.c',
@ -144,6 +146,7 @@ sway_sources = files(
'commands/unmark.c', 'commands/unmark.c',
'commands/urgent.c', 'commands/urgent.c',
'commands/workspace.c', 'commands/workspace.c',
'commands/workspace_gesture.c',
'commands/workspace_layout.c', 'commands/workspace_layout.c',
'commands/ws_auto_back_and_forth.c', 'commands/ws_auto_back_and_forth.c',
'commands/xwayland.c', 'commands/xwayland.c',

View file

@ -63,33 +63,38 @@
#define SWAY_LAYER_SHELL_VERSION 4 #define SWAY_LAYER_SHELL_VERSION 4
#if WLR_HAS_DRM_BACKEND #if WLR_HAS_DRM_BACKEND
static void handle_drm_lease_request(struct wl_listener *listener, void *data) { static void handle_drm_lease_request(struct wl_listener *listener, void *data)
{
/* We only offer non-desktop outputs, but in the future we might want to do /* We only offer non-desktop outputs, but in the future we might want to do
* more logic here. */ * more logic here. */
struct wlr_drm_lease_request_v1 *req = data; struct wlr_drm_lease_request_v1 *req = data;
struct wlr_drm_lease_v1 *lease = wlr_drm_lease_request_v1_grant(req); struct wlr_drm_lease_v1 *lease = wlr_drm_lease_request_v1_grant(req);
if (!lease) { if (!lease)
{
sway_log(SWAY_ERROR, "Failed to grant lease request"); sway_log(SWAY_ERROR, "Failed to grant lease request");
wlr_drm_lease_request_v1_reject(req); wlr_drm_lease_request_v1_reject(req);
} }
} }
#endif #endif
static bool is_privileged(const struct wl_global *global) { static bool is_privileged(const struct wl_global *global)
{
#if WLR_HAS_DRM_BACKEND #if WLR_HAS_DRM_BACKEND
if (server.drm_lease_manager != NULL) { if (server.drm_lease_manager != NULL)
{
struct wlr_drm_lease_device_v1 *drm_lease_dev; struct wlr_drm_lease_device_v1 *drm_lease_dev;
wl_list_for_each(drm_lease_dev, &server.drm_lease_manager->devices, link) { wl_list_for_each(drm_lease_dev, &server.drm_lease_manager->devices, link)
if (drm_lease_dev->global == global) { {
if (drm_lease_dev->global == global)
{
return true; return true;
} }
} }
} }
#endif #endif
return return global == server.output_manager_v1->global ||
global == server.output_manager_v1->global ||
global == server.output_power_manager_v1->global || global == server.output_power_manager_v1->global ||
global == server.input_method->global || global == server.input_method->global ||
global == server.foreign_toplevel_manager->global || global == server.foreign_toplevel_manager->global ||
@ -107,10 +112,12 @@ static bool is_privileged(const struct wl_global *global) {
} }
static bool filter_global(const struct wl_client *client, static bool filter_global(const struct wl_client *client,
const struct wl_global *global, void *data) { const struct wl_global *global, void *data)
{
#if HAVE_XWAYLAND #if HAVE_XWAYLAND
struct wlr_xwayland *xwayland = server.xwayland.wlr_xwayland; struct wlr_xwayland *xwayland = server.xwayland.wlr_xwayland;
if (xwayland && global == xwayland->shell_v1->global) { if (xwayland && global == xwayland->shell_v1->global)
{
return xwayland->server != NULL && client == xwayland->server->client; return xwayland->server != NULL && client == xwayland->server->client;
} }
#endif #endif
@ -120,14 +127,16 @@ static bool filter_global(const struct wl_client *client,
const struct wlr_security_context_v1_state *security_context = const struct wlr_security_context_v1_state *security_context =
wlr_security_context_manager_v1_lookup_client( wlr_security_context_manager_v1_lookup_client(
server.security_context_manager_v1, (struct wl_client *)client); server.security_context_manager_v1, (struct wl_client *)client);
if (is_privileged(global)) { if (is_privileged(global))
{
return security_context == NULL; return security_context == NULL;
} }
return true; return true;
} }
bool server_init(struct sway_server *server) { bool server_init(struct sway_server *server)
{
sway_log(SWAY_DEBUG, "Initializing Wayland server"); sway_log(SWAY_DEBUG, "Initializing Wayland server");
server->wl_display = wl_display_create(); server->wl_display = wl_display_create();
server->wl_event_loop = wl_display_get_event_loop(server->wl_display); server->wl_event_loop = wl_display_get_event_loop(server->wl_display);
@ -135,20 +144,23 @@ bool server_init(struct sway_server *server) {
wl_display_set_global_filter(server->wl_display, filter_global, NULL); wl_display_set_global_filter(server->wl_display, filter_global, NULL);
server->backend = wlr_backend_autocreate(server->wl_display, &server->session); server->backend = wlr_backend_autocreate(server->wl_display, &server->session);
if (!server->backend) { if (!server->backend)
{
sway_log(SWAY_ERROR, "Unable to create backend"); sway_log(SWAY_ERROR, "Unable to create backend");
return false; return false;
} }
server->renderer = fx_renderer_create(server->backend); server->renderer = fx_renderer_create(server->backend);
if (!server->renderer) { if (!server->renderer)
{
sway_log(SWAY_ERROR, "Failed to create fx_renderer"); sway_log(SWAY_ERROR, "Failed to create fx_renderer");
return false; return false;
} }
wlr_renderer_init_wl_shm(server->renderer, server->wl_display); wlr_renderer_init_wl_shm(server->renderer, server->wl_display);
if (wlr_renderer_get_dmabuf_texture_formats(server->renderer) != NULL) { if (wlr_renderer_get_dmabuf_texture_formats(server->renderer) != NULL)
{
wlr_drm_create(server->wl_display, server->renderer); wlr_drm_create(server->wl_display, server->renderer);
server->linux_dmabuf_v1 = wlr_linux_dmabuf_v1_create_with_renderer( server->linux_dmabuf_v1 = wlr_linux_dmabuf_v1_create_with_renderer(
server->wl_display, 4, server->renderer); server->wl_display, 4, server->renderer);
@ -156,7 +168,8 @@ bool server_init(struct sway_server *server) {
server->allocator = wlr_allocator_autocreate(server->backend, server->allocator = wlr_allocator_autocreate(server->backend,
server->renderer); server->renderer);
if (!server->allocator) { if (!server->allocator)
{
sway_log(SWAY_ERROR, "Failed to create allocator"); sway_log(SWAY_ERROR, "Failed to create allocator");
return false; return false;
} }
@ -256,13 +269,16 @@ bool server_init(struct sway_server *server) {
sway_session_lock_init(); sway_session_lock_init();
#if WLR_HAS_DRM_BACKEND #if WLR_HAS_DRM_BACKEND
server->drm_lease_manager= server->drm_lease_manager =
wlr_drm_lease_v1_manager_create(server->wl_display, server->backend); wlr_drm_lease_v1_manager_create(server->wl_display, server->backend);
if (server->drm_lease_manager) { if (server->drm_lease_manager)
{
server->drm_lease_request.notify = handle_drm_lease_request; server->drm_lease_request.notify = handle_drm_lease_request;
wl_signal_add(&server->drm_lease_manager->events.request, wl_signal_add(&server->drm_lease_manager->events.request,
&server->drm_lease_request); &server->drm_lease_request);
} else { }
else
{
sway_log(SWAY_DEBUG, "Failed to create wlr_drm_lease_device_v1"); sway_log(SWAY_DEBUG, "Failed to create wlr_drm_lease_device_v1");
sway_log(SWAY_INFO, "VR will not be available"); sway_log(SWAY_INFO, "VR will not be available");
} }
@ -302,26 +318,32 @@ bool server_init(struct sway_server *server) {
// Avoid using "wayland-0" as display socket // Avoid using "wayland-0" as display socket
char name_candidate[16]; char name_candidate[16];
for (unsigned int i = 1; i <= 32; ++i) { for (unsigned int i = 1; i <= 32; ++i)
{
snprintf(name_candidate, sizeof(name_candidate), "wayland-%u", i); snprintf(name_candidate, sizeof(name_candidate), "wayland-%u", i);
if (wl_display_add_socket(server->wl_display, name_candidate) >= 0) { if (wl_display_add_socket(server->wl_display, name_candidate) >= 0)
{
server->socket = strdup(name_candidate); server->socket = strdup(name_candidate);
break; break;
} }
} }
if (!server->socket) { if (!server->socket)
{
sway_log(SWAY_ERROR, "Unable to open wayland socket"); sway_log(SWAY_ERROR, "Unable to open wayland socket");
wlr_backend_destroy(server->backend); wlr_backend_destroy(server->backend);
return false; return false;
} }
server->headless_backend = wlr_headless_backend_create(server->wl_display); server->headless_backend = wlr_headless_backend_create(server->wl_display);
if (!server->headless_backend) { if (!server->headless_backend)
{
sway_log(SWAY_ERROR, "Failed to create secondary headless backend"); sway_log(SWAY_ERROR, "Failed to create secondary headless backend");
wlr_backend_destroy(server->backend); wlr_backend_destroy(server->backend);
return false; return false;
} else { }
else
{
wlr_multi_backend_add(server->backend, server->headless_backend); wlr_multi_backend_add(server->backend, server->headless_backend);
} }
@ -331,7 +353,8 @@ bool server_init(struct sway_server *server) {
root->fallback_output = output_create(wlr_output); root->fallback_output = output_create(wlr_output);
// This may have been set already via -Dtxn-timeout // This may have been set already via -Dtxn-timeout
if (!server->txn_timeout_ms) { if (!server->txn_timeout_ms)
{
server->txn_timeout_ms = 200; server->txn_timeout_ms = 200;
} }
@ -343,7 +366,8 @@ bool server_init(struct sway_server *server) {
return true; return true;
} }
void server_fini(struct sway_server *server) { void server_fini(struct sway_server *server)
{
// TODO: free sway-specific resources // TODO: free sway-specific resources
#if HAVE_XWAYLAND #if HAVE_XWAYLAND
wlr_xwayland_destroy(server->xwayland.wlr_xwayland); wlr_xwayland_destroy(server->xwayland.wlr_xwayland);
@ -353,18 +377,23 @@ void server_fini(struct sway_server *server) {
list_free(server->dirty_nodes); list_free(server->dirty_nodes);
} }
bool server_start(struct sway_server *server) { bool server_start(struct sway_server *server)
{
#if HAVE_XWAYLAND #if HAVE_XWAYLAND
if (config->xwayland != XWAYLAND_MODE_DISABLED) { if (config->xwayland != XWAYLAND_MODE_DISABLED)
{
sway_log(SWAY_DEBUG, "Initializing Xwayland (lazy=%d)", sway_log(SWAY_DEBUG, "Initializing Xwayland (lazy=%d)",
config->xwayland == XWAYLAND_MODE_LAZY); config->xwayland == XWAYLAND_MODE_LAZY);
server->xwayland.wlr_xwayland = server->xwayland.wlr_xwayland =
wlr_xwayland_create(server->wl_display, server->compositor, wlr_xwayland_create(server->wl_display, server->compositor,
config->xwayland == XWAYLAND_MODE_LAZY); config->xwayland == XWAYLAND_MODE_LAZY);
if (!server->xwayland.wlr_xwayland) { if (!server->xwayland.wlr_xwayland)
{
sway_log(SWAY_ERROR, "Failed to start Xwayland"); sway_log(SWAY_ERROR, "Failed to start Xwayland");
unsetenv("DISPLAY"); unsetenv("DISPLAY");
} else { }
else
{
wl_signal_add(&server->xwayland.wlr_xwayland->events.new_surface, wl_signal_add(&server->xwayland.wlr_xwayland->events.new_surface,
&server->xwayland_surface); &server->xwayland_surface);
server->xwayland_surface.notify = handle_xwayland_surface; server->xwayland_surface.notify = handle_xwayland_surface;
@ -379,13 +408,15 @@ bool server_start(struct sway_server *server) {
} }
#endif #endif
if (config->primary_selection) { if (config->primary_selection)
{
wlr_primary_selection_v1_device_manager_create(server->wl_display); wlr_primary_selection_v1_device_manager_create(server->wl_display);
} }
sway_log(SWAY_INFO, "Starting backend on wayland display '%s'", sway_log(SWAY_INFO, "Starting backend on wayland display '%s'",
server->socket); server->socket);
if (!wlr_backend_start(server->backend)) { if (!wlr_backend_start(server->backend))
{
sway_log(SWAY_ERROR, "Failed to start backend"); sway_log(SWAY_ERROR, "Failed to start backend");
wlr_backend_destroy(server->backend); wlr_backend_destroy(server->backend);
return false; return false;
@ -394,7 +425,8 @@ bool server_start(struct sway_server *server) {
return true; return true;
} }
void server_run(struct sway_server *server) { void server_run(struct sway_server *server)
{
sway_log(SWAY_INFO, "Running compositor on wayland display '%s'", sway_log(SWAY_INFO, "Running compositor on wayland display '%s'",
server->socket); server->socket);
wl_display_run(server->wl_display); wl_display_run(server->wl_display);

View file

@ -548,6 +548,61 @@ runtime.
``` ```
*bindworkspacegesture* [--input-device=<device>] [--no-warn] \
[--inverted] [<fingers>][: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
```
*workspace_gesture_spring_size* <value>
Adjusts the workspace gestures spring size. Can use values between
0 (disabled) and 100 while 50 is the default value.
*workspace_gesture_wrap_around* <enable|disable>
Sets whether or not the workspace gesture should wrap around when trying to
swipe past the first/last workspace. Disables the
_workspace_gesture_spring_size_ config option. Disabled by default.
*workspace_gesture_threshold* <value>
Adjusts the swipe threshold of switching workspaces. A lower value makes it
easier to switch workspaces. Accepts values between 0.1 (small swipes) and
0.9 (large swipes). The default value is set to 0.5.
*client.background* <color> *client.background* <color>
This command is ignored and is only present for i3 compatibility. This command is ignored and is only present for i3 compatibility.

View file

@ -350,11 +350,17 @@ static struct sway_container *view_container_at(struct sway_node *parent,
} }
struct sway_container *container = parent->sway_container; struct sway_container *container = parent->sway_container;
int gaps = 0;
if (container->current.workspace) {
gaps = container->current.workspace->gaps_inner;
}
struct wlr_box box = { struct wlr_box box = {
.x = container->pending.x, .x = container->pending.x - (gaps / 2),
.y = container->pending.y, .y = container->pending.y - (gaps / 2),
.width = container->pending.width, .width = container->pending.width + gaps,
.height = container->pending.height, .height = container->pending.height + gaps,
}; };
if (wlr_box_contains_point(&box, lx, ly)) { if (wlr_box_contains_point(&box, lx, ly)) {

View file

@ -354,11 +354,11 @@ struct sway_workspace *workspace_by_name(const char *name) {
if (current && strcmp(name, "prev") == 0) { if (current && strcmp(name, "prev") == 0) {
return workspace_prev(current); return workspace_prev(current);
} else if (current && strcmp(name, "prev_on_output") == 0) { } else if (current && strcmp(name, "prev_on_output") == 0) {
return workspace_output_prev(current); return workspace_output_prev(current, true);
} else if (current && strcmp(name, "next") == 0) { } else if (current && strcmp(name, "next") == 0) {
return workspace_next(current); return workspace_next(current);
} else if (current && strcmp(name, "next_on_output") == 0) { } else if (current && strcmp(name, "next_on_output") == 0) {
return workspace_output_next(current); return workspace_output_next(current, true);
} else if (strcmp(name, "current") == 0) { } else if (strcmp(name, "current") == 0) {
return current; return current;
} else if (strcasecmp(name, "back_and_forth") == 0) { } else if (strcasecmp(name, "back_and_forth") == 0) {
@ -521,7 +521,7 @@ struct sway_workspace *workspace_next(struct sway_workspace *workspace) {
* otherwise the next one is returned. * otherwise the next one is returned.
*/ */
static struct sway_workspace *workspace_output_prev_next_impl( static struct sway_workspace *workspace_output_prev_next_impl(
struct sway_output *output, int dir) { struct sway_output *output, int dir, bool should_wrap) {
struct sway_seat *seat = input_manager_current_seat(); struct sway_seat *seat = input_manager_current_seat();
struct sway_workspace *workspace = seat_get_focused_workspace(seat); struct sway_workspace *workspace = seat_get_focused_workspace(seat);
if (!workspace) { if (!workspace) {
@ -531,17 +531,27 @@ static struct sway_workspace *workspace_output_prev_next_impl(
} }
int index = list_find(output->workspaces, workspace); int index = list_find(output->workspaces, workspace);
size_t new_index = wrap(index + dir, output->workspaces->length); size_t new_index;
if (!should_wrap) {
new_index = index += dir;
if (index < 0 || index >= output->workspaces->length) {
return NULL;
}
} else {
new_index = wrap(index + dir, output->workspaces->length);
}
return output->workspaces->items[new_index]; return output->workspaces->items[new_index];
} }
struct sway_workspace *workspace_output_next(struct sway_workspace *current) { struct sway_workspace *workspace_output_next(struct sway_workspace *current,
return workspace_output_prev_next_impl(current->output, 1); bool should_wrap) {
return workspace_output_prev_next_impl(current->output, 1, should_wrap);
} }
struct sway_workspace *workspace_output_prev(struct sway_workspace *current) { struct sway_workspace *workspace_output_prev(struct sway_workspace *current,
return workspace_output_prev_next_impl(current->output, -1); bool should_wrap) {
return workspace_output_prev_next_impl(current->output, -1, should_wrap);
} }
struct sway_workspace *workspace_auto_back_and_forth( struct sway_workspace *workspace_auto_back_and_forth(
@ -571,6 +581,7 @@ bool workspace_switch(struct sway_workspace *workspace) {
sway_log(SWAY_DEBUG, "Switching to workspace %p:%s", sway_log(SWAY_DEBUG, "Switching to workspace %p:%s",
workspace, workspace->name); workspace, workspace->name);
workspace->output->workspace_scroll = workspace_scroll_get_default();
struct sway_node *next = seat_get_focus_inactive(seat, &workspace->node); struct sway_node *next = seat_get_focus_inactive(seat, &workspace->node);
if (next == NULL) { if (next == NULL) {
next = &workspace->node; next = &workspace->node;