This commit is contained in:
Fabian Specht 2025-02-14 08:25:16 +00:00 committed by GitHub
commit 80d1fc5d10
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 214 additions and 21 deletions

View file

@ -185,6 +185,7 @@ sway_cmd cmd_sticky;
sway_cmd cmd_swaybg_command;
sway_cmd cmd_swaynag_command;
sway_cmd cmd_swap;
sway_cmd cmd_swap_workspace_content;
sway_cmd cmd_tiling_drag;
sway_cmd cmd_tiling_drag_threshold;
sway_cmd cmd_title_align;

View file

@ -163,6 +163,8 @@ void container_begin_destroy(struct sway_container *con);
struct sway_container *container_find_child(struct sway_container *container,
bool (*test)(struct sway_container *view, void *data), void *data);
struct sway_container *container_get_first_view(struct sway_container *container);
void container_for_each_child(struct sway_container *container,
void (*f)(struct sway_container *container, void *data), void *data);

View file

@ -1,7 +1,9 @@
#include <strings.h>
#include <ctype.h>
#include "config.h"
#include "log.h"
#include "sway/commands.h"
#include "sway/ipc-server.h"
#include "sway/output.h"
#include "sway/tree/arrange.h"
#include "sway/tree/container.h"
@ -11,7 +13,162 @@
#include "stringop.h"
static const char expected_syntax[] =
"Expected 'swap container with id|con_id|mark <arg>'";
"Expected 'swap (container with id|con_id|mark <arg>) |\n\t(wokspace with [number] <name>)'";
/*
* prepare the containers inside a workspace to be moved to another
* workspace. This includes setting a few properties of the containers
* as well as ipc events
*/
static void handle_container_after_move(struct sway_container *container,
void *data) {
node_set_dirty(&container->node);
container_update_representation(container);
struct sway_workspace *old_workspace = container->pending.workspace;
struct sway_output *old_output = container->pending.workspace->output;
struct sway_workspace *destination = data;
container->pending.workspace = destination;
// handle floating containers here by updating their position
if (container_is_floating(container)) {
if (old_output == destination->output ||
container->pending.fullscreen_mode) return;
struct wlr_box workspace_box, old_workspace_box;
workspace_get_box(destination, &workspace_box);
workspace_get_box(old_workspace, &old_workspace_box);
floating_fix_coordinates(container,
&old_workspace_box, &workspace_box);
if (!container->scratchpad || !destination->output) return;
struct wlr_box output_box;
output_get_box(destination->output, &output_box);
container->transform = workspace_box;
}
if (container->view) {
ipc_event_window(container, "move");
}
}
/*
* swap the properties necessary to preserve the layout
* as well as their respective contents
*/
static void swap_workspace_properties(struct sway_workspace *cur_ws,
struct sway_workspace *oth_ws) {
struct sway_workspace cur_ws_cpy = *cur_ws;
cur_ws->tiling = oth_ws->tiling;
oth_ws->tiling = cur_ws_cpy.tiling;
cur_ws->floating = oth_ws->floating;
oth_ws->floating = cur_ws_cpy.floating;
cur_ws->layout = oth_ws->layout;
oth_ws->layout = cur_ws_cpy.layout;
cur_ws->prev_split_layout = oth_ws->prev_split_layout;
oth_ws->prev_split_layout = cur_ws_cpy.prev_split_layout;
cur_ws->current_gaps = oth_ws->current_gaps;
oth_ws->current_gaps = cur_ws_cpy.current_gaps;
cur_ws->gaps_outer = oth_ws->gaps_outer;
oth_ws->gaps_outer = cur_ws_cpy.gaps_outer;
cur_ws->gaps_inner = oth_ws->gaps_inner;
oth_ws->gaps_inner = cur_ws_cpy.gaps_inner;
}
static void set_new_focus(struct sway_workspace *ws, struct sway_seat *seat) {
if (ws->tiling->length) {
// this needs to be more specific (focus not just every container,
// but single windows
struct sway_container *container = ws->tiling->items[0];
struct sway_container *first_view = container_get_first_view(container);
if (!first_view) {
seat_set_focus(seat, &ws->node);
}
seat_set_focus(seat, &first_view->node);
} else if (ws->floating->length) {
seat_set_focus(seat, ws->floating->items[0]);
} else {
seat_set_focus(seat, &ws->node);
}
}
static struct cmd_results *swap_workspaces(int argc, char **argv) {
char *ws_name = NULL;
struct sway_workspace *oth_ws;
if (strcasecmp(argv[2], "number") == 0) {
if (!isdigit(argv[3][0])) {
return cmd_results_new(CMD_INVALID,
"Invalid workspace number '%s'", argv[3]);
}
ws_name = join_args(argv + 3, argc - 3);
oth_ws = workspace_by_number(ws_name);
} else {
ws_name = join_args(argv + 2, argc - 2);
oth_ws = workspace_by_name(ws_name);
}
if (!oth_ws) {
oth_ws = workspace_create(NULL, ws_name);
if (!oth_ws) {
return cmd_results_new(CMD_FAILURE,
"Unable to create new workspace");
}
}
free(ws_name);
// second workspace is the one currently focused
struct sway_workspace *cur_ws = config->handler_context.workspace;
if (!cur_ws) {
return cmd_results_new(CMD_FAILURE, NULL);
}
// exit early if there is nothing to swap
if (cur_ws == oth_ws) return cmd_results_new(CMD_SUCCESS, NULL);
// save seat to set the focus later
struct sway_seat *seat = config->handler_context.seat;
swap_workspace_properties(cur_ws, oth_ws);
node_set_dirty(&cur_ws->node);
node_set_dirty(&oth_ws->node);
workspace_update_representation(cur_ws);
workspace_update_representation(oth_ws);
// before rearranging the workspaces we have to set a few properties
// such as dirty
workspace_for_each_container(cur_ws, handle_container_after_move, cur_ws);
workspace_for_each_container(oth_ws, handle_container_after_move, oth_ws);
workspace_detect_urgent(cur_ws);
workspace_detect_urgent(oth_ws);
// after swapping we set the focus on the first container in the current
// workspace or the workspace itself if there is no container
set_new_focus(cur_ws, seat);
// destroy other workspace in case it is empty
workspace_consider_destroy(oth_ws);
// update both affected workspaces
arrange_workspace(cur_ws);
arrange_workspace(oth_ws);
return cmd_results_new(CMD_SUCCESS, NULL);
}
static bool test_con_id(struct sway_container *container, void *data) {
size_t *con_id = data;
@ -34,19 +191,8 @@ static bool test_mark(struct sway_container *container, void *mark) {
return false;
}
struct cmd_results *cmd_swap(int argc, char **argv) {
static struct cmd_results *swap_containers(int argc, char **argv) {
struct cmd_results *error = NULL;
if ((error = checkarg(argc, "swap", EXPECTED_AT_LEAST, 4))) {
return error;
}
if (!root->outputs->length) {
return cmd_results_new(CMD_INVALID,
"Can't run this command while there's no outputs connected.");
}
if (strcasecmp(argv[0], "container") || strcasecmp(argv[1], "with")) {
return cmd_results_new(CMD_INVALID, "%s", expected_syntax);
}
struct sway_container *current = config->handler_context.container;
struct sway_container *other = NULL;
@ -105,3 +251,22 @@ struct cmd_results *cmd_swap(int argc, char **argv) {
return cmd_results_new(CMD_SUCCESS, NULL);
}
struct cmd_results *cmd_swap(int argc, char **argv) {
struct cmd_results *error = NULL;
if ((error = checkarg(argc, "swap", EXPECTED_AT_LEAST, 4))) {
return error;
}
if (!root->outputs->length) {
return cmd_results_new(CMD_INVALID,
"Can't run this command while there's no outputs connected.");
}
if (strcasecmp(argv[0], "container") == 0 && strcasecmp(argv[1], "with") == 0) {
return swap_containers(argc, argv);
} else if (strcasecmp(argv[0], "workspace") == 0 && strcasecmp(argv[1], "with") == 0) {
return swap_workspaces(argc, argv);
}
return cmd_results_new(CMD_INVALID, "%s", expected_syntax);
}

View file

@ -360,14 +360,20 @@ set|plus|minus|toggle <amount>
"Sticks" a floating window to the current output so that it shows up on all
workspaces.
*swap* container with id|con_id|mark <arg>
Swaps the position, geometry, and fullscreen status of two containers. The
first container can be selected either by criteria or focus. The second
container can be selected by _id_, _con_id_, or _mark_. _id_ can only be
used with xwayland views. If the first container has focus, it will retain
focus unless it is moved to a different workspace or the second container
becomes fullscreen on the same workspace as the first container. In either
of those cases, the second container will gain focus.
*swap* (container with id|con_id|mark <arg>) |
(workspace with [number] <number>)
If using the container keyword, swaps the position, geometry, and
fullscreen status of two containers. The first container can be selected
either by criteria or focus. The second container can be selected by
_id_, _con_id_, or _mark_. _id_ can only be used with xwayland views.
If the first container has focus, it will retain focus unless it is moved
to a different workspace or the second container becomes fullscreen on the
same workspace as the first container. In either of those cases, the
second container will gain focus.
If using the workspace keyword, swaps the content of the currently focused
workspace with the content of the workspace specified as an argument.
*title_format* <format>
Sets the format of window titles. The following placeholders may be used:

View file

@ -604,6 +604,25 @@ struct sway_container *container_find_child(struct sway_container *container,
return NULL;
}
struct sway_container *container_get_first_view(struct sway_container *container) {
if (container->view) {
return container;
}
if (container->pending.children) {
for (int i = 0; i < container->pending.children->length; ++i) {
struct sway_container *child = container->pending.children->items[i];
struct sway_container *view = container_get_first_view(child);
if (view) {
return view;
}
}
}
return NULL;
}
void container_for_each_child(struct sway_container *container,
void (*f)(struct sway_container *container, void *data),
void *data) {