Add the swap_workspace command

The added command allow the user to swap the contents of two workspaces.

Co-authored-by: Arne <ge47com@tum.de>
This commit is contained in:
Fabian Specht 2024-01-12 16:02:12 +01:00
parent c5fd8c050f
commit c2eaf01767
No known key found for this signature in database
GPG key ID: 3E324CC0E6232463
7 changed files with 211 additions and 0 deletions

View file

@ -184,6 +184,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

@ -164,6 +164,9 @@ struct sway_container *tiling_container_at(
struct sway_node *parent, double lx, double ly,
struct wlr_surface **surface, double *sx, double *sy);
void container_get_first_view(struct sway_container *container,
struct sway_container **view);
void container_for_each_child(struct sway_container *container,
void (*f)(struct sway_container *container, void *data), void *data);

View file

@ -137,6 +137,7 @@ static const struct cmd_handler command_handlers[] = {
{ "splitv", cmd_splitv },
{ "sticky", cmd_sticky },
{ "swap", cmd_swap },
{ "swap_workspace", cmd_swap_workspace_content },
{ "title_format", cmd_title_format },
{ "unmark", cmd_unmark },
{ "urgent", cmd_urgent },

View file

@ -0,0 +1,187 @@
#include "log.h"
#include "stringop.h"
#include "sway/output.h"
#include "sway/input/input-manager.h"
#include "sway/input/seat.h"
#include "sway/tree/arrange.h"
#include "sway/tree/container.h"
#include "sway/tree/view.h"
#include "sway/tree/workspace.h"
#include "sway/commands.h"
#include "sway/ipc-server.h"
#include "stringop.h"
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <ctype.h>
/*
* 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 *first_view;
struct sway_container *container = ws->tiling->items[0];
container_get_first_view(container, &first_view);
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);
}
}
/*
* swap the contents of the currently focused workspace with the content
* of the workspace specified in the args
*
* syntax: swap_workspace with <name|number x>
*/
struct cmd_results *cmd_swap_workspace_content(int argc, char **argv) {
// parse arguments
if (argc < 2) {
return cmd_results_new(CMD_INVALID, "syntax not supported");
}
if (strcasecmp(argv[0], "with") != 0) {
return cmd_results_new(CMD_FAILURE, "Invalid syntax");
}
char *ws_name = NULL;
struct sway_workspace *oth_ws;
if (strcasecmp(argv[1], "number") == 0) {
if (!isdigit(argv[2][0])) {
return cmd_results_new(CMD_INVALID,
"Invalid workspace number '%s'", argv[2]);
}
ws_name = join_args(argv + 2, argc - 2);
oth_ws = workspace_by_number(ws_name);
} else {
ws_name = join_args(argv + 1, argc - 1);
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);
}

View file

@ -111,6 +111,7 @@ sway_sources = files(
'commands/swaybg_command.c',
'commands/swaynag_command.c',
'commands/swap.c',
'commands/swap_workspace.c',
'commands/tiling_drag.c',
'commands/tiling_drag_threshold.c',
'commands/title_align.c',

View file

@ -353,6 +353,10 @@ set|plus|minus|toggle <amount>
becomes fullscreen on the same workspace as the first container. In either
of those cases, the second container will gain focus.
*swap_workspace* with [number] <name>
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

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