diff options
-rw-r--r-- | include/sway/commands.h | 1 | ||||
-rw-r--r-- | include/sway/tree/layout.h | 2 | ||||
-rw-r--r-- | sway/commands.c | 1 | ||||
-rw-r--r-- | sway/commands/swap.c | 80 | ||||
-rw-r--r-- | sway/meson.build | 1 | ||||
-rw-r--r-- | sway/sway.5.scd | 9 | ||||
-rw-r--r-- | sway/tree/layout.c | 124 |
7 files changed, 218 insertions, 0 deletions
diff --git a/include/sway/commands.h b/include/sway/commands.h index d39ac56c..365068ae 100644 --- a/include/sway/commands.h +++ b/include/sway/commands.h | |||
@@ -144,6 +144,7 @@ sway_cmd cmd_splitt; | |||
144 | sway_cmd cmd_splitv; | 144 | sway_cmd cmd_splitv; |
145 | sway_cmd cmd_sticky; | 145 | sway_cmd cmd_sticky; |
146 | sway_cmd cmd_swaybg_command; | 146 | sway_cmd cmd_swaybg_command; |
147 | sway_cmd cmd_swap; | ||
147 | sway_cmd cmd_title_format; | 148 | sway_cmd cmd_title_format; |
148 | sway_cmd cmd_unmark; | 149 | sway_cmd cmd_unmark; |
149 | sway_cmd cmd_workspace; | 150 | sway_cmd cmd_workspace; |
diff --git a/include/sway/tree/layout.h b/include/sway/tree/layout.h index cc999871..2e0f2abf 100644 --- a/include/sway/tree/layout.h +++ b/include/sway/tree/layout.h | |||
@@ -69,4 +69,6 @@ struct sway_container *container_split(struct sway_container *child, | |||
69 | void container_recursive_resize(struct sway_container *container, | 69 | void container_recursive_resize(struct sway_container *container, |
70 | double amount, enum resize_edge edge); | 70 | double amount, enum resize_edge edge); |
71 | 71 | ||
72 | void container_swap(struct sway_container *con1, struct sway_container *con2); | ||
73 | |||
72 | #endif | 74 | #endif |
diff --git a/sway/commands.c b/sway/commands.c index 6cba0a1c..c3728afd 100644 --- a/sway/commands.c +++ b/sway/commands.c | |||
@@ -186,6 +186,7 @@ static struct cmd_handler command_handlers[] = { | |||
186 | { "splith", cmd_splith }, | 186 | { "splith", cmd_splith }, |
187 | { "splitt", cmd_splitt }, | 187 | { "splitt", cmd_splitt }, |
188 | { "splitv", cmd_splitv }, | 188 | { "splitv", cmd_splitv }, |
189 | { "swap", cmd_swap }, | ||
189 | { "title_format", cmd_title_format }, | 190 | { "title_format", cmd_title_format }, |
190 | { "unmark", cmd_unmark }, | 191 | { "unmark", cmd_unmark }, |
191 | }; | 192 | }; |
diff --git a/sway/commands/swap.c b/sway/commands/swap.c new file mode 100644 index 00000000..e925ad33 --- /dev/null +++ b/sway/commands/swap.c | |||
@@ -0,0 +1,80 @@ | |||
1 | #include <strings.h> | ||
2 | #include <wlr/util/log.h> | ||
3 | #include "sway/commands.h" | ||
4 | #include "sway/tree/layout.h" | ||
5 | #include "sway/tree/view.h" | ||
6 | #include "stringop.h" | ||
7 | |||
8 | static const char* EXPECTED_SYNTAX = | ||
9 | "Expected 'swap container with id|con_id|mark <arg>'"; | ||
10 | |||
11 | static bool test_con_id(struct sway_container *container, void *con_id) { | ||
12 | return container->id == (size_t)con_id; | ||
13 | } | ||
14 | |||
15 | static bool test_id(struct sway_container *container, void *id) { | ||
16 | xcb_window_t *wid = id; | ||
17 | return (container->type == C_VIEW | ||
18 | && container->sway_view->type == SWAY_VIEW_XWAYLAND | ||
19 | && container->sway_view->wlr_xwayland_surface->window_id == *wid); | ||
20 | } | ||
21 | |||
22 | static bool test_mark(struct sway_container *container, void *mark) { | ||
23 | if (container->type == C_VIEW && container->sway_view->marks->length) { | ||
24 | return !list_seq_find(container->sway_view->marks, | ||
25 | (int (*)(const void *, const void *))strcmp, mark); | ||
26 | } | ||
27 | return false; | ||
28 | } | ||
29 | |||
30 | struct cmd_results *cmd_swap(int argc, char **argv) { | ||
31 | struct cmd_results *error = NULL; | ||
32 | if ((error = checkarg(argc, "swap", EXPECTED_AT_LEAST, 4))) { | ||
33 | return error; | ||
34 | } | ||
35 | |||
36 | if (strcasecmp(argv[0], "container") || strcasecmp(argv[1], "with")) { | ||
37 | return cmd_results_new(CMD_INVALID, "swap", EXPECTED_SYNTAX); | ||
38 | } | ||
39 | |||
40 | struct sway_container *current = config->handler_context.current_container; | ||
41 | struct sway_container *other; | ||
42 | |||
43 | char *value = join_args(argv + 3, argc - 3); | ||
44 | if (strcasecmp(argv[2], "id") == 0) { | ||
45 | xcb_window_t id = strtol(value, NULL, 0); | ||
46 | other = container_find(&root_container, test_id, (void *)&id); | ||
47 | } else if (strcasecmp(argv[2], "con_id") == 0) { | ||
48 | size_t con_id = atoi(value); | ||
49 | other = container_find(&root_container, test_con_id, (void *)con_id); | ||
50 | } else if (strcasecmp(argv[2], "mark") == 0) { | ||
51 | other = container_find(&root_container, test_mark, (void *)value); | ||
52 | } else { | ||
53 | free(value); | ||
54 | return cmd_results_new(CMD_INVALID, "swap", EXPECTED_SYNTAX); | ||
55 | } | ||
56 | |||
57 | if (!other) { | ||
58 | error = cmd_results_new(CMD_FAILURE, "swap", | ||
59 | "Failed to find %s '%s'", argv[2], value); | ||
60 | } else if (current->type < C_CONTAINER || other->type < C_CONTAINER) { | ||
61 | error = cmd_results_new(CMD_FAILURE, "swap", | ||
62 | "Can only swap with containers and views"); | ||
63 | } else if (container_has_anscestor(current, other) | ||
64 | || container_has_anscestor(other, current)) { | ||
65 | error = cmd_results_new(CMD_FAILURE, "swap", | ||
66 | "Cannot swap ancestor and descendant"); | ||
67 | } else if (current->layout == L_FLOATING || other->layout == L_FLOATING) { | ||
68 | error = cmd_results_new(CMD_FAILURE, "swap", | ||
69 | "Swapping with floating containers is not supported"); | ||
70 | } | ||
71 | |||
72 | free(value); | ||
73 | |||
74 | if (error) { | ||
75 | return error; | ||
76 | } | ||
77 | |||
78 | container_swap(current, other); | ||
79 | return cmd_results_new(CMD_SUCCESS, NULL, NULL); | ||
80 | } | ||
diff --git a/sway/meson.build b/sway/meson.build index 72347d51..9c942e8e 100644 --- a/sway/meson.build +++ b/sway/meson.build | |||
@@ -63,6 +63,7 @@ sway_sources = files( | |||
63 | 'commands/show_marks.c', | 63 | 'commands/show_marks.c', |
64 | 'commands/split.c', | 64 | 'commands/split.c', |
65 | 'commands/swaybg_command.c', | 65 | 'commands/swaybg_command.c', |
66 | 'commands/swap.c', | ||
66 | 'commands/title_format.c', | 67 | 'commands/title_format.c', |
67 | 'commands/unmark.c', | 68 | 'commands/unmark.c', |
68 | 'commands/workspace.c', | 69 | 'commands/workspace.c', |
diff --git a/sway/sway.5.scd b/sway/sway.5.scd index ff138562..25a5eef5 100644 --- a/sway/sway.5.scd +++ b/sway/sway.5.scd | |||
@@ -167,6 +167,15 @@ They are expected to be used with *bindsym* or at runtime through *swaymsg*(1). | |||
167 | "Sticks" a floating window to the current output so that it shows up on all | 167 | "Sticks" a floating window to the current output so that it shows up on all |
168 | workspaces. | 168 | workspaces. |
169 | 169 | ||
170 | *swap* container with id|con\_id|mark <arg> | ||
171 | Swaps the position, geometry, and fullscreen status of two containers. The | ||
172 | first container can be selected either by criteria or focus. The second | ||
173 | container can be selected by _id_, _con\_id_, or _mark_. _id_ can only be | ||
174 | used with xwayland views. If the first container has focus, it will retain | ||
175 | focus unless it is moved to a different workspace or the second container | ||
176 | becomes fullscreen on the same workspace as the first container. In either | ||
177 | of those cases, the second container will gain focus. | ||
178 | |||
170 | The following commands may be used either in the configuration file or at | 179 | The following commands may be used either in the configuration file or at |
171 | runtime. | 180 | runtime. |
172 | 181 | ||
diff --git a/sway/tree/layout.c b/sway/tree/layout.c index 21cec529..624d5516 100644 --- a/sway/tree/layout.c +++ b/sway/tree/layout.c | |||
@@ -882,3 +882,127 @@ void container_recursive_resize(struct sway_container *container, | |||
882 | } | 882 | } |
883 | } | 883 | } |
884 | } | 884 | } |
885 | |||
886 | static void swap_places(struct sway_container *con1, | ||
887 | struct sway_container *con2) { | ||
888 | struct sway_container *temp = malloc(sizeof(struct sway_container)); | ||
889 | temp->x = con1->x; | ||
890 | temp->y = con1->y; | ||
891 | temp->width = con1->width; | ||
892 | temp->height = con1->height; | ||
893 | temp->parent = con1->parent; | ||
894 | |||
895 | con1->x = con2->x; | ||
896 | con1->y = con2->y; | ||
897 | con1->width = con2->width; | ||
898 | con1->height = con2->height; | ||
899 | |||
900 | con2->x = temp->x; | ||
901 | con2->y = temp->y; | ||
902 | con2->width = temp->width; | ||
903 | con2->height = temp->height; | ||
904 | |||
905 | int temp_index = index_child(con1); | ||
906 | container_insert_child(con2->parent, con1, index_child(con2)); | ||
907 | container_insert_child(temp->parent, con2, temp_index); | ||
908 | |||
909 | free(temp); | ||
910 | } | ||
911 | |||
912 | static void swap_focus(struct sway_container *con1, | ||
913 | struct sway_container *con2, struct sway_seat *seat, | ||
914 | struct sway_container *focus) { | ||
915 | if (focus == con1 || focus == con2) { | ||
916 | struct sway_container *ws1 = container_parent(con1, C_WORKSPACE); | ||
917 | struct sway_container *ws2 = container_parent(con2, C_WORKSPACE); | ||
918 | if (focus == con1 && (con2->parent->layout == L_TABBED | ||
919 | || con2->parent->layout == L_STACKED)) { | ||
920 | if (workspace_is_visible(ws2)) { | ||
921 | seat_set_focus_warp(seat, con2, false); | ||
922 | } | ||
923 | seat_set_focus(seat, ws1 != ws2 ? con2 : con1); | ||
924 | } else if (focus == con2 && (con1->parent->layout == L_TABBED | ||
925 | || con1->parent->layout == L_STACKED)) { | ||
926 | if (workspace_is_visible(ws1)) { | ||
927 | seat_set_focus_warp(seat, con1, false); | ||
928 | } | ||
929 | seat_set_focus(seat, ws1 != ws2 ? con1 : con2); | ||
930 | } else if (ws1 != ws2) { | ||
931 | seat_set_focus(seat, focus == con1 ? con2 : con1); | ||
932 | } else { | ||
933 | seat_set_focus(seat, focus); | ||
934 | } | ||
935 | } else { | ||
936 | seat_set_focus(seat, focus); | ||
937 | } | ||
938 | } | ||
939 | |||
940 | void container_swap(struct sway_container *con1, struct sway_container *con2) { | ||
941 | if (!sway_assert(con1 && con2, "Cannot swap with nothing")) { | ||
942 | return; | ||
943 | } | ||
944 | if (!sway_assert(con1->type >= C_CONTAINER && con2->type >= C_CONTAINER, | ||
945 | "Can only swap containers and views")) { | ||
946 | return; | ||
947 | } | ||
948 | if (!sway_assert(!container_has_anscestor(con1, con2) | ||
949 | && !container_has_anscestor(con2, con1), | ||
950 | "Cannot swap anscestor and descendant")) { | ||
951 | return; | ||
952 | } | ||
953 | if (!sway_assert(con1->layout != L_FLOATING && con2->layout != L_FLOATING, | ||
954 | "Swapping with floating containers is not supported")) { | ||
955 | return; | ||
956 | } | ||
957 | |||
958 | wlr_log(L_DEBUG, "Swapping containers %zu and %zu", con1->id, con2->id); | ||
959 | |||
960 | int fs1 = con1->type == C_VIEW && con1->sway_view->is_fullscreen; | ||
961 | int fs2 = con2->type == C_VIEW && con2->sway_view->is_fullscreen; | ||
962 | if (fs1) { | ||
963 | view_set_fullscreen(con1->sway_view, false); | ||
964 | } | ||
965 | if (fs2) { | ||
966 | view_set_fullscreen(con2->sway_view, false); | ||
967 | } | ||
968 | |||
969 | struct sway_seat *seat = input_manager_get_default_seat(input_manager); | ||
970 | struct sway_container *focus = seat_get_focus(seat); | ||
971 | struct sway_container *vis1 = container_parent( | ||
972 | seat_get_focus_inactive(seat, container_parent(con1, C_OUTPUT)), | ||
973 | C_WORKSPACE); | ||
974 | struct sway_container *vis2 = container_parent( | ||
975 | seat_get_focus_inactive(seat, container_parent(con2, C_OUTPUT)), | ||
976 | C_WORKSPACE); | ||
977 | |||
978 | char *stored_prev_name = NULL; | ||
979 | if (prev_workspace_name) { | ||
980 | stored_prev_name = strdup(prev_workspace_name); | ||
981 | } | ||
982 | |||
983 | swap_places(con1, con2); | ||
984 | |||
985 | if (!workspace_is_visible(vis1)) { | ||
986 | seat_set_focus(seat, seat_get_focus_inactive(seat, vis1)); | ||
987 | } | ||
988 | if (!workspace_is_visible(vis2)) { | ||
989 | seat_set_focus(seat, seat_get_focus_inactive(seat, vis2)); | ||
990 | } | ||
991 | |||
992 | swap_focus(con1, con2, seat, focus); | ||
993 | |||
994 | if (stored_prev_name) { | ||
995 | free(prev_workspace_name); | ||
996 | prev_workspace_name = stored_prev_name; | ||
997 | } | ||
998 | |||
999 | arrange_children_of(con1->parent); | ||
1000 | arrange_children_of(con2->parent); | ||
1001 | |||
1002 | if (fs1 && con2->type == C_VIEW) { | ||
1003 | view_set_fullscreen(con2->sway_view, true); | ||
1004 | } | ||
1005 | if (fs2 && con1->type == C_VIEW) { | ||
1006 | view_set_fullscreen(con1->sway_view, true); | ||
1007 | } | ||
1008 | } | ||