aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--common/util.c21
-rw-r--r--completions/fish/sway.fish10
-rw-r--r--completions/fish/swaylock.fish11
-rw-r--r--completions/fish/swaymsg.fish8
-rw-r--r--include/sway/config.h2
-rw-r--r--include/sway/output.h8
-rw-r--r--include/sway/tree/arrange.h14
-rw-r--r--include/sway/tree/container.h64
-rw-r--r--include/sway/tree/layout.h53
-rw-r--r--include/sway/tree/root.h4
-rw-r--r--include/sway/tree/view.h4
-rw-r--r--include/sway/tree/workspace.h13
-rw-r--r--include/util.h14
-rw-r--r--meson.build12
-rw-r--r--meson_options.txt1
-rw-r--r--sway/commands/exec_always.c1
-rw-r--r--sway/commands/floating.c1
-rw-r--r--sway/commands/focus.c195
-rw-r--r--sway/commands/fullscreen.c1
-rw-r--r--sway/commands/hide_edge_borders.c1
-rw-r--r--sway/commands/layout.c2
-rw-r--r--sway/commands/move.c479
-rw-r--r--sway/commands/output.c1
-rw-r--r--sway/commands/resize.c21
-rw-r--r--sway/commands/show_marks.c1
-rw-r--r--sway/commands/sticky.c6
-rw-r--r--sway/commands/swap.c128
-rw-r--r--sway/commands/unmark.c1
-rw-r--r--sway/commands/urgent.c1
-rw-r--r--sway/config.c2
-rw-r--r--sway/config/output.c11
-rw-r--r--sway/criteria.c1
-rw-r--r--sway/debug-tree.c2
-rw-r--r--sway/desktop/layer_shell.c1
-rw-r--r--sway/desktop/output.c9
-rw-r--r--sway/desktop/render.c17
-rw-r--r--sway/desktop/transaction.c17
-rw-r--r--sway/desktop/xdg_shell.c4
-rw-r--r--sway/desktop/xdg_shell_v6.c4
-rw-r--r--sway/desktop/xwayland.c3
-rw-r--r--sway/input/cursor.c1
-rw-r--r--sway/input/seat.c107
-rw-r--r--sway/ipc-server.c3
-rw-r--r--sway/main.c2
-rw-r--r--sway/meson.build1
-rw-r--r--sway/server.c2
-rw-r--r--sway/sway.5.scd11
-rw-r--r--sway/tree/arrange.c274
-rw-r--r--sway/tree/container.c578
-rw-r--r--sway/tree/layout.c1019
-rw-r--r--sway/tree/output.c126
-rw-r--r--sway/tree/root.c5
-rw-r--r--sway/tree/view.c46
-rw-r--r--sway/tree/workspace.c106
54 files changed, 1752 insertions, 1678 deletions
diff --git a/common/util.c b/common/util.c
index 467aa4b5..561b3804 100644
--- a/common/util.c
+++ b/common/util.c
@@ -175,3 +175,24 @@ failed:
175 free(current); 175 free(current);
176 return NULL; 176 return NULL;
177} 177}
178
179bool sway_dir_to_wlr(enum movement_direction dir, enum wlr_direction *out) {
180 switch (dir) {
181 case MOVE_UP:
182 *out = WLR_DIRECTION_UP;
183 break;
184 case MOVE_DOWN:
185 *out = WLR_DIRECTION_DOWN;
186 break;
187 case MOVE_LEFT:
188 *out = WLR_DIRECTION_LEFT;
189 break;
190 case MOVE_RIGHT:
191 *out = WLR_DIRECTION_RIGHT;
192 break;
193 default:
194 return false;
195 }
196
197 return true;
198}
diff --git a/completions/fish/sway.fish b/completions/fish/sway.fish
new file mode 100644
index 00000000..31165ef4
--- /dev/null
+++ b/completions/fish/sway.fish
@@ -0,0 +1,10 @@
1# sway(1) completion
2
3complete -c sway -s h -l help --description "Show help message and quit."
4complete -c sway -s c -l config --description "Specifies a config file."
5complete -c sway -s C -l validate --description "Check the validity of the config file, then exit."
6complete -c sway -s d -l debug --description "Enables full logging, including debug information."
7complete -c sway -s v -l version --description "Show the version number and quit."
8complete -c sway -s V -l verbose --description "Enables more verbose logging."
9complete -c sway -l get-socketpath --description "Gets the IPC socket path and prints it, then exits."
10
diff --git a/completions/fish/swaylock.fish b/completions/fish/swaylock.fish
new file mode 100644
index 00000000..965a22d2
--- /dev/null
+++ b/completions/fish/swaylock.fish
@@ -0,0 +1,11 @@
1# swaylock(1) completion
2
3complete -c swaylock -s h -l help --description "Show help message and quit."
4complete -c swaylock -s c -l color --description "Turn the screen into the given color. If -i is used, this sets the background of the image into the given color. Defaults to white (ffffff), or transparent (00000000) if an image is in use."
5complete -c swaylock -s f -l daemonize --description "Fork into the background after spawning. Note: this is the default bahavior of i3lock."
6complete -c swaylock -s i -l image --description "Display the given image, optionally on the given output. Use -c to set a background color."
7complete -c swaylock -l scaling --description "Scaling mode for images: stretch, fill, fit, center, or tile."
8complete -c swaylock -s t -l tiling --description "Same as --scaling=tile."
9complete -c swaylock -s u -l no-unlock-indicator --description "Disable the unlock indicator."
10complete -c swaylock -s v -l version --description "Show the version number and quit."
11complete -c swaylock -l socket --description "Use the specified socket path. Othherwise, swaymsg will as sway where the socket is (which is the value of $SWAYSOCK, then of $I350CK)."
diff --git a/completions/fish/swaymsg.fish b/completions/fish/swaymsg.fish
new file mode 100644
index 00000000..e798db77
--- /dev/null
+++ b/completions/fish/swaymsg.fish
@@ -0,0 +1,8 @@
1# swaymsg(1) completion
2
3complete -c swaymsg -s h -l help --description "Show help message and quit."
4complete -c swaymsg -s q -l quiet --description "Sends the IPC message but does not print the response from sway."
5complete -c swaymsg -s r -l raw --description "Use raw output even if using tty."
6complete -c swaymsg -s s -l socket --description "Use the specified socket path. Otherwise, swaymsg will ask where the socket is (which is the value of $SWAYSOCK, then of $I3SOCK)."
7complete -c swaymsg -s t -l type --description "Specify the type of IPC message."
8complete -c swaymsg -s v -l version --description "Print the version (of swaymsg) and quit."
diff --git a/include/sway/config.h b/include/sway/config.h
index c2eaea1b..18d10faa 100644
--- a/include/sway/config.h
+++ b/include/sway/config.h
@@ -8,8 +8,8 @@
8#include <xkbcommon/xkbcommon.h> 8#include <xkbcommon/xkbcommon.h>
9#include "list.h" 9#include "list.h"
10#include "swaynag.h" 10#include "swaynag.h"
11#include "tree/layout.h"
12#include "tree/container.h" 11#include "tree/container.h"
12#include "sway/tree/root.h"
13#include "wlr-layer-shell-unstable-v1-protocol.h" 13#include "wlr-layer-shell-unstable-v1-protocol.h"
14 14
15// TODO: Refactor this shit 15// TODO: Refactor this shit
diff --git a/include/sway/output.h b/include/sway/output.h
index d0d034b3..651fdfe7 100644
--- a/include/sway/output.h
+++ b/include/sway/output.h
@@ -39,6 +39,14 @@ struct sway_output {
39 } events; 39 } events;
40}; 40};
41 41
42struct sway_container *output_create(struct sway_output *sway_output);
43
44void output_destroy(struct sway_container *output);
45
46void output_begin_destroy(struct sway_container *output);
47
48struct sway_container *output_from_wlr_output(struct wlr_output *output);
49
42typedef void (*sway_surface_iterator_func_t)(struct sway_output *output, 50typedef void (*sway_surface_iterator_func_t)(struct sway_output *output,
43 struct wlr_surface *surface, struct wlr_box *box, float rotation, 51 struct wlr_surface *surface, struct wlr_box *box, float rotation,
44 void *user_data); 52 void *user_data);
diff --git a/include/sway/tree/arrange.h b/include/sway/tree/arrange.h
index d6abcc81..f47e8db5 100644
--- a/include/sway/tree/arrange.h
+++ b/include/sway/tree/arrange.h
@@ -1,18 +1,16 @@
1#ifndef _SWAY_ARRANGE_H 1#ifndef _SWAY_ARRANGE_H
2#define _SWAY_ARRANGE_H 2#define _SWAY_ARRANGE_H
3#include "sway/desktop/transaction.h"
4 3
5struct sway_container; 4struct sway_container;
6 5
7// Remove gaps around container 6void arrange_container(struct sway_container *container);
8void remove_gaps(struct sway_container *c);
9 7
10// Add gaps around container 8void arrange_workspace(struct sway_container *workspace);
11void add_gaps(struct sway_container *c); 9
10void arrange_output(struct sway_container *output);
11
12void arrange_root(void);
12 13
13/**
14 * Arrange layout for all the children of the given container.
15 */
16void arrange_windows(struct sway_container *container); 14void arrange_windows(struct sway_container *container);
17 15
18#endif 16#endif
diff --git a/include/sway/tree/container.h b/include/sway/tree/container.h
index cd886cd0..e4071cfe 100644
--- a/include/sway/tree/container.h
+++ b/include/sway/tree/container.h
@@ -53,6 +53,9 @@ struct sway_output;
53struct sway_workspace; 53struct sway_workspace;
54struct sway_view; 54struct sway_view;
55 55
56enum movement_direction;
57enum wlr_direction;
58
56struct sway_container_state { 59struct sway_container_state {
57 // Container/swayc properties 60 // Container/swayc properties
58 enum sway_container_layout layout; 61 enum sway_container_layout layout;
@@ -176,27 +179,6 @@ struct sway_container *container_create(enum sway_container_type type);
176 179
177const char *container_type_to_str(enum sway_container_type type); 180const char *container_type_to_str(enum sway_container_type type);
178 181
179struct sway_container *output_create(struct sway_output *sway_output);
180
181/**
182 * Create a new container container. A container container can be a a child of
183 * a workspace container or another container container.
184 */
185struct sway_container *container_container_create();
186
187/**
188 * Create a new output. Outputs are children of the root container and have no
189 * order in the tree structure.
190 */
191struct sway_container *output_create(struct sway_output *sway_output);
192
193/**
194 * Create a new workspace container. Workspaces are children of an output
195 * container and are ordered alphabetically by name.
196 */
197struct sway_container *workspace_create(struct sway_container *output,
198 const char *name);
199
200/* 182/*
201 * Create a new view container. A view can be a child of a workspace container 183 * Create a new view container. A view can be a child of a workspace container
202 * or a container container and are rendered in the order and structure of 184 * or a container container and are rendered in the order and structure of
@@ -205,9 +187,9 @@ struct sway_container *workspace_create(struct sway_container *output,
205struct sway_container *container_view_create( 187struct sway_container *container_view_create(
206 struct sway_container *sibling, struct sway_view *sway_view); 188 struct sway_container *sibling, struct sway_view *sway_view);
207 189
208void container_free(struct sway_container *cont); 190void container_destroy(struct sway_container *con);
209 191
210struct sway_container *container_destroy(struct sway_container *container); 192void container_begin_destroy(struct sway_container *con);
211 193
212struct sway_container *container_close(struct sway_container *container); 194struct sway_container *container_close(struct sway_container *container);
213 195
@@ -255,10 +237,7 @@ void container_update_textures_recursive(struct sway_container *con);
255 237
256void container_damage_whole(struct sway_container *container); 238void container_damage_whole(struct sway_container *container);
257 239
258bool container_reap_empty(struct sway_container *con); 240struct sway_container *container_reap_empty(struct sway_container *con);
259
260struct sway_container *container_reap_empty_recursive(
261 struct sway_container *con);
262 241
263struct sway_container *container_flatten(struct sway_container *container); 242struct sway_container *container_flatten(struct sway_container *container);
264 243
@@ -359,4 +338,35 @@ struct sway_output *container_get_effective_output(struct sway_container *con);
359 338
360void container_discover_outputs(struct sway_container *con); 339void container_discover_outputs(struct sway_container *con);
361 340
341void container_remove_gaps(struct sway_container *container);
342
343void container_add_gaps(struct sway_container *container);
344
345int container_sibling_index(const struct sway_container *child);
346
347void container_handle_fullscreen_reparent(struct sway_container *con,
348 struct sway_container *old_parent);
349
350void container_add_child(struct sway_container *parent,
351 struct sway_container *child);
352
353void container_insert_child(struct sway_container *parent,
354 struct sway_container *child, int i);
355
356struct sway_container *container_add_sibling(struct sway_container *parent,
357 struct sway_container *child);
358
359struct sway_container *container_remove_child(struct sway_container *child);
360
361struct sway_container *container_replace_child(struct sway_container *child,
362 struct sway_container *new_child);
363
364bool sway_dir_to_wlr(enum movement_direction dir, enum wlr_direction *out);
365
366enum sway_container_layout container_get_default_layout(
367 struct sway_container *con);
368
369struct sway_container *container_split(struct sway_container *child,
370 enum sway_container_layout layout);
371
362#endif 372#endif
diff --git a/include/sway/tree/layout.h b/include/sway/tree/layout.h
deleted file mode 100644
index 519189d9..00000000
--- a/include/sway/tree/layout.h
+++ /dev/null
@@ -1,53 +0,0 @@
1#ifndef _SWAY_LAYOUT_H
2#define _SWAY_LAYOUT_H
3#include <wlr/types/wlr_output_layout.h>
4#include <wlr/render/wlr_texture.h>
5#include "sway/tree/container.h"
6#include "sway/tree/root.h"
7#include "config.h"
8
9enum movement_direction {
10 MOVE_LEFT,
11 MOVE_RIGHT,
12 MOVE_UP,
13 MOVE_DOWN,
14 MOVE_PARENT,
15 MOVE_CHILD,
16};
17
18enum wlr_edges;
19
20struct sway_container;
21
22void container_add_child(struct sway_container *parent,
23 struct sway_container *child);
24
25struct sway_container *container_add_sibling(struct sway_container *parent,
26 struct sway_container *child);
27
28struct sway_container *container_remove_child(struct sway_container *child);
29
30struct sway_container *container_replace_child(struct sway_container *child,
31 struct sway_container *new_child);
32
33void container_move_to(struct sway_container* container,
34 struct sway_container* destination);
35
36void container_move(struct sway_container *container,
37 enum movement_direction dir, int move_amt);
38
39enum sway_container_layout container_get_default_layout(
40 struct sway_container *con);
41
42struct sway_container *container_get_in_direction(struct sway_container
43 *container, struct sway_seat *seat, enum movement_direction dir);
44
45struct sway_container *container_split(struct sway_container *child,
46 enum sway_container_layout layout);
47
48void container_recursive_resize(struct sway_container *container,
49 double amount, enum wlr_edges edge);
50
51void container_swap(struct sway_container *con1, struct sway_container *con2);
52
53#endif
diff --git a/include/sway/tree/root.h b/include/sway/tree/root.h
index d1f04a96..ec6516c9 100644
--- a/include/sway/tree/root.h
+++ b/include/sway/tree/root.h
@@ -21,9 +21,11 @@ struct sway_root {
21 21
22 struct wlr_texture *debug_tree; 22 struct wlr_texture *debug_tree;
23 23
24 struct wl_list outputs; // sway_output::link 24 // Includes disabled outputs
25 struct wl_list all_outputs; // sway_output::link
25 26
26 list_t *scratchpad; // struct sway_container 27 list_t *scratchpad; // struct sway_container
28 list_t *saved_workspaces; // For when there's no connected outputs
27 29
28 struct { 30 struct {
29 struct wl_signal new_container; 31 struct wl_signal new_container;
diff --git a/include/sway/tree/view.h b/include/sway/tree/view.h
index 5fdecc2b..f73ce571 100644
--- a/include/sway/tree/view.h
+++ b/include/sway/tree/view.h
@@ -284,10 +284,10 @@ void view_for_each_popup(struct sway_view *view,
284void view_init(struct sway_view *view, enum sway_view_type type, 284void view_init(struct sway_view *view, enum sway_view_type type,
285 const struct sway_view_impl *impl); 285 const struct sway_view_impl *impl);
286 286
287void view_free(struct sway_view *view);
288
289void view_destroy(struct sway_view *view); 287void view_destroy(struct sway_view *view);
290 288
289void view_begin_destroy(struct sway_view *view);
290
291void view_map(struct sway_view *view, struct wlr_surface *wlr_surface); 291void view_map(struct sway_view *view, struct wlr_surface *wlr_surface);
292 292
293void view_unmap(struct sway_view *view); 293void view_unmap(struct sway_view *view);
diff --git a/include/sway/tree/workspace.h b/include/sway/tree/workspace.h
index 35c91017..04325919 100644
--- a/include/sway/tree/workspace.h
+++ b/include/sway/tree/workspace.h
@@ -18,6 +18,15 @@ extern char *prev_workspace_name;
18 18
19struct sway_container *workspace_get_initial_output(const char *name); 19struct sway_container *workspace_get_initial_output(const char *name);
20 20
21struct sway_container *workspace_create(struct sway_container *output,
22 const char *name);
23
24void workspace_destroy(struct sway_container *workspace);
25
26void workspace_begin_destroy(struct sway_container *workspace);
27
28void workspace_consider_destroy(struct sway_container *ws);
29
21char *workspace_next_name(const char *output_name); 30char *workspace_next_name(const char *output_name);
22 31
23bool workspace_switch(struct sway_container *workspace, 32bool workspace_switch(struct sway_container *workspace,
@@ -66,4 +75,8 @@ struct sway_container *workspace_wrap_children(struct sway_container *ws);
66void workspace_add_floating(struct sway_container *workspace, 75void workspace_add_floating(struct sway_container *workspace,
67 struct sway_container *con); 76 struct sway_container *con);
68 77
78void workspace_remove_gaps(struct sway_container *ws);
79
80void workspace_add_gaps(struct sway_container *ws);
81
69#endif 82#endif
diff --git a/include/util.h b/include/util.h
index 9277fa6e..46ed1533 100644
--- a/include/util.h
+++ b/include/util.h
@@ -4,9 +4,19 @@
4#include <stdint.h> 4#include <stdint.h>
5#include <stdbool.h> 5#include <stdbool.h>
6#include <unistd.h> 6#include <unistd.h>
7#include <sys/types.h> 7#include <sys/types.h>
8#include <wlr/types/wlr_output_layout.h>
8#include <xkbcommon/xkbcommon.h> 9#include <xkbcommon/xkbcommon.h>
9 10
11enum movement_direction {
12 MOVE_LEFT,
13 MOVE_RIGHT,
14 MOVE_UP,
15 MOVE_DOWN,
16 MOVE_PARENT,
17 MOVE_CHILD,
18};
19
10/** 20/**
11 * Wrap i into the range [0, max[ 21 * Wrap i into the range [0, max[
12 */ 22 */
@@ -71,4 +81,6 @@ char* resolve_path(const char* path);
71char *b64_encode(const char* binaryData, size_t len, size_t *flen); 81char *b64_encode(const char* binaryData, size_t len, size_t *flen);
72unsigned char *b64_decode(const char *ascii, size_t len, size_t *flen); 82unsigned char *b64_decode(const char *ascii, size_t len, size_t *flen);
73 83
84bool sway_dir_to_wlr(enum movement_direction dir, enum wlr_direction *out);
85
74#endif 86#endif
diff --git a/meson.build b/meson.build
index 2a020323..d5b33e6b 100644
--- a/meson.build
+++ b/meson.build
@@ -12,6 +12,7 @@ project(
12add_project_arguments('-Wno-unused-parameter', language: 'c') 12add_project_arguments('-Wno-unused-parameter', language: 'c')
13add_project_arguments('-Wno-unused-function', language: 'c') 13add_project_arguments('-Wno-unused-function', language: 'c')
14add_project_arguments('-Wno-unused-result', language: 'c') 14add_project_arguments('-Wno-unused-result', language: 'c')
15add_project_arguments('-DWL_HIDE_DEPRECATED', language: 'c')
15add_project_arguments('-DWLR_USE_UNSTABLE', language: 'c') 16add_project_arguments('-DWLR_USE_UNSTABLE', language: 'c')
16 17
17cc = meson.get_compiler('c') 18cc = meson.get_compiler('c')
@@ -205,3 +206,14 @@ if (get_option('bash_completions'))
205 206
206 install_data(bash_files, install_dir: bash_install_dir) 207 install_data(bash_files, install_dir: bash_install_dir)
207endif 208endif
209
210if (get_option('fish_completions'))
211 fish_files = files(
212 'completions/fish/sway.fish',
213 'completions/fish/swaylock.fish',
214 'completions/fish/swaymsg.fish',
215 )
216 fish_install_dir = datadir + '/fish/completions'
217
218 install_data(fish_files, install_dir: fish_install_dir)
219endif
diff --git a/meson_options.txt b/meson_options.txt
index 7a23c206..5e54607f 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -2,4 +2,5 @@ option('sway_version', type : 'string', description: 'The version string reporte
2option('default_wallpaper', type: 'boolean', value: true, description: 'Install the default wallpaper.') 2option('default_wallpaper', type: 'boolean', value: true, description: 'Install the default wallpaper.')
3option('zsh_completions', type: 'boolean', value: true, description: 'Install zsh shell completions.') 3option('zsh_completions', type: 'boolean', value: true, description: 'Install zsh shell completions.')
4option('bash_completions', type: 'boolean', value: true, description: 'Install bash shell completions.') 4option('bash_completions', type: 'boolean', value: true, description: 'Install bash shell completions.')
5option('fish_completions', type: 'boolean', value: true, description: 'Install fish shell completions.')
5option('enable-xwayland', type: 'boolean', value: true, description: 'Enable support for X11 applications') 6option('enable-xwayland', type: 'boolean', value: true, description: 'Enable support for X11 applications')
diff --git a/sway/commands/exec_always.c b/sway/commands/exec_always.c
index 5ce7919b..bc07c2aa 100644
--- a/sway/commands/exec_always.c
+++ b/sway/commands/exec_always.c
@@ -8,6 +8,7 @@
8#include "sway/commands.h" 8#include "sway/commands.h"
9#include "sway/config.h" 9#include "sway/config.h"
10#include "sway/tree/container.h" 10#include "sway/tree/container.h"
11#include "sway/tree/root.h"
11#include "sway/tree/workspace.h" 12#include "sway/tree/workspace.h"
12#include "log.h" 13#include "log.h"
13#include "stringop.h" 14#include "stringop.h"
diff --git a/sway/commands/floating.c b/sway/commands/floating.c
index beafd9fb..436376e3 100644
--- a/sway/commands/floating.c
+++ b/sway/commands/floating.c
@@ -6,7 +6,6 @@
6#include "sway/output.h" 6#include "sway/output.h"
7#include "sway/tree/arrange.h" 7#include "sway/tree/arrange.h"
8#include "sway/tree/container.h" 8#include "sway/tree/container.h"
9#include "sway/tree/layout.h"
10#include "sway/tree/view.h" 9#include "sway/tree/view.h"
11#include "sway/tree/workspace.h" 10#include "sway/tree/workspace.h"
12#include "list.h" 11#include "list.h"
diff --git a/sway/commands/focus.c b/sway/commands/focus.c
index 6659a683..f342e524 100644
--- a/sway/commands/focus.c
+++ b/sway/commands/focus.c
@@ -6,9 +6,11 @@
6#include "sway/input/seat.h" 6#include "sway/input/seat.h"
7#include "sway/output.h" 7#include "sway/output.h"
8#include "sway/tree/arrange.h" 8#include "sway/tree/arrange.h"
9#include "sway/tree/root.h"
9#include "sway/tree/view.h" 10#include "sway/tree/view.h"
10#include "sway/tree/workspace.h" 11#include "sway/tree/workspace.h"
11#include "stringop.h" 12#include "stringop.h"
13#include "util.h"
12 14
13static bool parse_movement_direction(const char *name, 15static bool parse_movement_direction(const char *name,
14 enum movement_direction *out) { 16 enum movement_direction *out) {
@@ -31,6 +33,199 @@ static bool parse_movement_direction(const char *name,
31 return true; 33 return true;
32} 34}
33 35
36/**
37 * Get swayc in the direction of newly entered output.
38 */
39static struct sway_container *get_swayc_in_output_direction(
40 struct sway_container *output, enum movement_direction dir,
41 struct sway_seat *seat) {
42 if (!output) {
43 return NULL;
44 }
45
46 struct sway_container *ws = seat_get_focus_inactive(seat, output);
47 if (ws->type != C_WORKSPACE) {
48 ws = container_parent(ws, C_WORKSPACE);
49 }
50
51 if (ws == NULL) {
52 wlr_log(WLR_ERROR, "got an output without a workspace");
53 return NULL;
54 }
55
56 if (ws->children->length > 0) {
57 switch (dir) {
58 case MOVE_LEFT:
59 if (ws->layout == L_HORIZ || ws->layout == L_TABBED) {
60 // get most right child of new output
61 return ws->children->items[ws->children->length-1];
62 } else {
63 return seat_get_focus_inactive(seat, ws);
64 }
65 case MOVE_RIGHT:
66 if (ws->layout == L_HORIZ || ws->layout == L_TABBED) {
67 // get most left child of new output
68 return ws->children->items[0];
69 } else {
70 return seat_get_focus_inactive(seat, ws);
71 }
72 case MOVE_UP:
73 case MOVE_DOWN: {
74 struct sway_container *focused =
75 seat_get_focus_inactive(seat, ws);
76 if (focused && focused->parent) {
77 struct sway_container *parent = focused->parent;
78 if (parent->layout == L_VERT) {
79 if (dir == MOVE_UP) {
80 // get child furthest down on new output
81 int idx = parent->children->length - 1;
82 return parent->children->items[idx];
83 } else if (dir == MOVE_DOWN) {
84 // get child furthest up on new output
85 return parent->children->items[0];
86 }
87 }
88 return focused;
89 }
90 break;
91 }
92 default:
93 break;
94 }
95 }
96
97 return ws;
98}
99
100static struct sway_container *container_get_in_direction(
101 struct sway_container *container, struct sway_seat *seat,
102 enum movement_direction dir) {
103 struct sway_container *parent = container->parent;
104
105 if (dir == MOVE_CHILD) {
106 return seat_get_focus_inactive(seat, container);
107 }
108 if (container->is_fullscreen) {
109 if (dir == MOVE_PARENT) {
110 return NULL;
111 }
112 container = container_parent(container, C_OUTPUT);
113 parent = container->parent;
114 } else {
115 if (dir == MOVE_PARENT) {
116 if (parent->type == C_OUTPUT || container_is_floating(container)) {
117 return NULL;
118 } else {
119 return parent;
120 }
121 }
122 }
123
124 struct sway_container *wrap_candidate = NULL;
125 while (true) {
126 bool can_move = false;
127 int desired;
128 int idx = list_find(container->parent->children, container);
129 if (idx == -1) {
130 return NULL;
131 }
132 if (parent->type == C_ROOT) {
133 enum wlr_direction wlr_dir = 0;
134 if (!sway_assert(sway_dir_to_wlr(dir, &wlr_dir),
135 "got invalid direction: %d", dir)) {
136 return NULL;
137 }
138 int lx = container->x + container->width / 2;
139 int ly = container->y + container->height / 2;
140 struct wlr_output_layout *layout =
141 root_container.sway_root->output_layout;
142 struct wlr_output *wlr_adjacent =
143 wlr_output_layout_adjacent_output(layout, wlr_dir,
144 container->sway_output->wlr_output, lx, ly);
145 struct sway_container *adjacent =
146 output_from_wlr_output(wlr_adjacent);
147
148 if (!adjacent || adjacent == container) {
149 if (!wrap_candidate) {
150 return NULL;
151 }
152 return seat_get_focus_inactive_view(seat, wrap_candidate);
153 }
154 struct sway_container *next =
155 get_swayc_in_output_direction(adjacent, dir, seat);
156 if (next == NULL) {
157 return NULL;
158 }
159 struct sway_container *next_workspace = next;
160 if (next_workspace->type != C_WORKSPACE) {
161 next_workspace = container_parent(next_workspace, C_WORKSPACE);
162 }
163 sway_assert(next_workspace, "Next container has no workspace");
164 if (next_workspace->sway_workspace->fullscreen) {
165 return seat_get_focus_inactive(seat,
166 next_workspace->sway_workspace->fullscreen);
167 }
168 if (next->children && next->children->length) {
169 // TODO consider floating children as well
170 return seat_get_focus_inactive_view(seat, next);
171 } else {
172 return next;
173 }
174 } else {
175 if (dir == MOVE_LEFT || dir == MOVE_RIGHT) {
176 if (parent->layout == L_HORIZ || parent->layout == L_TABBED) {
177 can_move = true;
178 desired = idx + (dir == MOVE_LEFT ? -1 : 1);
179 }
180 } else {
181 if (parent->layout == L_VERT || parent->layout == L_STACKED) {
182 can_move = true;
183 desired = idx + (dir == MOVE_UP ? -1 : 1);
184 }
185 }
186 }
187
188 if (can_move) {
189 // TODO handle floating
190 if (desired < 0 || desired >= parent->children->length) {
191 can_move = false;
192 int len = parent->children->length;
193 if (config->focus_wrapping != WRAP_NO && !wrap_candidate
194 && len > 1) {
195 if (desired < 0) {
196 wrap_candidate = parent->children->items[len-1];
197 } else {
198 wrap_candidate = parent->children->items[0];
199 }
200 if (config->focus_wrapping == WRAP_FORCE) {
201 return seat_get_focus_inactive_view(seat,
202 wrap_candidate);
203 }
204 }
205 } else {
206 struct sway_container *desired_con =
207 parent->children->items[desired];
208 wlr_log(WLR_DEBUG,
209 "cont %d-%p dir %i sibling %d: %p", idx,
210 container, dir, desired, desired_con);
211 return seat_get_focus_inactive_view(seat, desired_con);
212 }
213 }
214
215 if (!can_move) {
216 container = parent;
217 parent = parent->parent;
218 if (!parent) {
219 // wrapping is the last chance
220 if (!wrap_candidate) {
221 return NULL;
222 }
223 return seat_get_focus_inactive_view(seat, wrap_candidate);
224 }
225 }
226 }
227}
228
34static struct cmd_results *focus_mode(struct sway_container *con, 229static struct cmd_results *focus_mode(struct sway_container *con,
35 struct sway_seat *seat, bool floating) { 230 struct sway_seat *seat, bool floating) {
36 struct sway_container *ws = con->type == C_WORKSPACE ? 231 struct sway_container *ws = con->type == C_WORKSPACE ?
diff --git a/sway/commands/fullscreen.c b/sway/commands/fullscreen.c
index a0661200..ac65dffb 100644
--- a/sway/commands/fullscreen.c
+++ b/sway/commands/fullscreen.c
@@ -5,7 +5,6 @@
5#include "sway/tree/container.h" 5#include "sway/tree/container.h"
6#include "sway/tree/view.h" 6#include "sway/tree/view.h"
7#include "sway/tree/workspace.h" 7#include "sway/tree/workspace.h"
8#include "sway/tree/layout.h"
9#include "util.h" 8#include "util.h"
10 9
11struct cmd_results *cmd_fullscreen(int argc, char **argv) { 10struct cmd_results *cmd_fullscreen(int argc, char **argv) {
diff --git a/sway/commands/hide_edge_borders.c b/sway/commands/hide_edge_borders.c
index d59c9fdb..e494f6aa 100644
--- a/sway/commands/hide_edge_borders.c
+++ b/sway/commands/hide_edge_borders.c
@@ -1,6 +1,7 @@
1#include "sway/commands.h" 1#include "sway/commands.h"
2#include "sway/config.h" 2#include "sway/config.h"
3#include "sway/tree/container.h" 3#include "sway/tree/container.h"
4#include "sway/tree/root.h"
4#include "sway/tree/view.h" 5#include "sway/tree/view.h"
5 6
6static void _configure_view(struct sway_container *con, void *data) { 7static void _configure_view(struct sway_container *con, void *data) {
diff --git a/sway/commands/layout.c b/sway/commands/layout.c
index f4e4dda9..a06832de 100644
--- a/sway/commands/layout.c
+++ b/sway/commands/layout.c
@@ -103,7 +103,7 @@ struct cmd_results *cmd_layout(int argc, char **argv) {
103 parent->prev_split_layout = prev; 103 parent->prev_split_layout = prev;
104 } 104 }
105 container_notify_subtree_changed(parent); 105 container_notify_subtree_changed(parent);
106 arrange_windows(parent); 106 arrange_windows(parent->parent);
107 } 107 }
108 108
109 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 109 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
diff --git a/sway/commands/move.c b/sway/commands/move.c
index c6dc0775..7b7cb8f3 100644
--- a/sway/commands/move.c
+++ b/sway/commands/move.c
@@ -19,6 +19,7 @@
19#include "stringop.h" 19#include "stringop.h"
20#include "list.h" 20#include "list.h"
21#include "log.h" 21#include "log.h"
22#include "util.h"
22 23
23static const char *expected_syntax = 24static const char *expected_syntax =
24 "Expected 'move <left|right|up|down> <[px] px>' or " 25 "Expected 'move <left|right|up|down> <[px] px>' or "
@@ -26,7 +27,20 @@ static const char *expected_syntax =
26 "'move <container|window|workspace> [to] output <name|direction>' or " 27 "'move <container|window|workspace> [to] output <name|direction>' or "
27 "'move <container|window> [to] mark <mark>'"; 28 "'move <container|window> [to] mark <mark>'";
28 29
29static struct sway_container *output_in_direction(const char *direction, 30enum wlr_direction opposite_direction(enum wlr_direction d) {
31 switch (d) {
32 case WLR_DIRECTION_UP:
33 return WLR_DIRECTION_DOWN;
34 case WLR_DIRECTION_DOWN:
35 return WLR_DIRECTION_UP;
36 case WLR_DIRECTION_RIGHT:
37 return WLR_DIRECTION_LEFT;
38 default:
39 return WLR_DIRECTION_RIGHT;
40 }
41}
42
43static struct sway_container *output_in_direction(const char *direction_string,
30 struct wlr_output *reference, int ref_lx, int ref_ly) { 44 struct wlr_output *reference, int ref_lx, int ref_ly) {
31 struct { 45 struct {
32 char *name; 46 char *name;
@@ -37,19 +51,440 @@ static struct sway_container *output_in_direction(const char *direction,
37 { "left", WLR_DIRECTION_LEFT }, 51 { "left", WLR_DIRECTION_LEFT },
38 { "right", WLR_DIRECTION_RIGHT }, 52 { "right", WLR_DIRECTION_RIGHT },
39 }; 53 };
54
55 enum wlr_direction direction = 0;
56
40 for (size_t i = 0; i < sizeof(names) / sizeof(names[0]); ++i) { 57 for (size_t i = 0; i < sizeof(names) / sizeof(names[0]); ++i) {
41 if (strcasecmp(names[i].name, direction) == 0) { 58 if (strcasecmp(names[i].name, direction_string) == 0) {
42 struct wlr_output *adjacent = wlr_output_layout_adjacent_output( 59 direction = names[i].direction;
60 break;
61 }
62 }
63
64 if (direction) {
65 struct wlr_output *target = wlr_output_layout_adjacent_output(
66 root_container.sway_root->output_layout,
67 direction, reference, ref_lx, ref_ly);
68
69 if (!target) {
70 target = wlr_output_layout_farthest_output(
43 root_container.sway_root->output_layout, 71 root_container.sway_root->output_layout,
44 names[i].direction, reference, ref_lx, ref_ly); 72 opposite_direction(direction), reference, ref_lx, ref_ly);
45 if (adjacent) { 73 }
46 struct sway_output *sway_output = adjacent->data; 74
47 return sway_output->swayc; 75 if (target) {
76 struct sway_output *sway_output = target->data;
77 return sway_output->swayc;
78 }
79 }
80
81 return output_by_name(direction_string);
82}
83
84static void container_move_to(struct sway_container *container,
85 struct sway_container *destination) {
86 if (!sway_assert(container->type == C_CONTAINER ||
87 container->type == C_VIEW, "Expected a container or view")) {
88 return;
89 }
90 if (container == destination
91 || container_has_ancestor(container, destination)) {
92 return;
93 }
94 struct sway_container *old_parent = NULL;
95 struct sway_container *new_parent = NULL;
96 if (container_is_floating(container)) {
97 // Resolve destination into a workspace
98 struct sway_container *new_ws = NULL;
99 if (destination->type == C_OUTPUT) {
100 new_ws = output_get_active_workspace(destination->sway_output);
101 } else if (destination->type == C_WORKSPACE) {
102 new_ws = destination;
103 } else {
104 new_ws = container_parent(destination, C_WORKSPACE);
105 }
106 if (!new_ws) {
107 // This can happen if the user has run "move container to mark foo",
108 // where mark foo is on a hidden scratchpad container.
109 return;
110 }
111 struct sway_container *old_output =
112 container_parent(container, C_OUTPUT);
113 old_parent = container_remove_child(container);
114 workspace_add_floating(new_ws, container);
115 container_handle_fullscreen_reparent(container, old_parent);
116 // If changing output, center it within the workspace
117 if (old_output != new_ws->parent && !container->is_fullscreen) {
118 container_floating_move_to_center(container);
119 }
120 } else {
121 old_parent = container_remove_child(container);
122 container->width = container->height = 0;
123 container->saved_width = container->saved_height = 0;
124
125 if (destination->type == C_VIEW) {
126 new_parent = container_add_sibling(destination, container);
127 } else {
128 new_parent = destination;
129 container_add_child(destination, container);
130 }
131 }
132
133 if (container->type == C_VIEW) {
134 ipc_event_window(container, "move");
135 }
136 container_notify_subtree_changed(old_parent);
137 container_notify_subtree_changed(new_parent);
138
139 // If view was moved to a fullscreen workspace, refocus the fullscreen view
140 struct sway_container *new_workspace = container;
141 if (new_workspace->type != C_WORKSPACE) {
142 new_workspace = container_parent(new_workspace, C_WORKSPACE);
143 }
144 if (new_workspace->sway_workspace->fullscreen) {
145 struct sway_seat *seat;
146 struct sway_container *focus, *focus_ws;
147 wl_list_for_each(seat, &input_manager->seats, link) {
148 focus = seat_get_focus(seat);
149 focus_ws = focus;
150 if (focus_ws->type != C_WORKSPACE) {
151 focus_ws = container_parent(focus_ws, C_WORKSPACE);
48 } 152 }
153 if (focus_ws == new_workspace) {
154 struct sway_container *new_focus = seat_get_focus_inactive(seat,
155 new_workspace->sway_workspace->fullscreen);
156 seat_set_focus(seat, new_focus);
157 }
158 }
159 }
160 // Update workspace urgent state
161 struct sway_container *old_workspace = old_parent;
162 if (old_workspace->type != C_WORKSPACE) {
163 old_workspace = container_parent(old_workspace, C_WORKSPACE);
164 }
165 if (new_workspace != old_workspace) {
166 workspace_detect_urgent(new_workspace);
167 if (old_workspace) {
168 workspace_detect_urgent(old_workspace);
169 }
170 }
171}
172
173static bool is_parallel(enum sway_container_layout layout,
174 enum movement_direction dir) {
175 switch (layout) {
176 case L_TABBED:
177 case L_HORIZ:
178 return dir == MOVE_LEFT || dir == MOVE_RIGHT;
179 case L_STACKED:
180 case L_VERT:
181 return dir == MOVE_UP || dir == MOVE_DOWN;
182 default:
183 return false;
184 }
185}
186
187static enum movement_direction invert_movement(enum movement_direction dir) {
188 switch (dir) {
189 case MOVE_LEFT:
190 return MOVE_RIGHT;
191 case MOVE_RIGHT:
192 return MOVE_LEFT;
193 case MOVE_UP:
194 return MOVE_DOWN;
195 case MOVE_DOWN:
196 return MOVE_UP;
197 default:
198 sway_assert(0, "This function expects left|right|up|down");
199 return MOVE_LEFT;
200 }
201}
202
203static int move_offs(enum movement_direction move_dir) {
204 return move_dir == MOVE_LEFT || move_dir == MOVE_UP ? -1 : 1;
205}
206
207/* Gets the index of the most extreme member based on the movement offset */
208static int container_limit(struct sway_container *container,
209 enum movement_direction move_dir) {
210 return move_offs(move_dir) < 0 ? 0 : container->children->length;
211}
212
213/* Takes one child, sets it aside, wraps the rest of the children in a new
214 * container, switches the layout of the workspace, and drops the child back in.
215 * In other words, rejigger it. */
216static void workspace_rejigger(struct sway_container *ws,
217 struct sway_container *child, enum movement_direction move_dir) {
218 struct sway_container *original_parent = child->parent;
219 struct sway_container *new_parent =
220 container_split(ws, ws->layout);
221
222 container_remove_child(child);
223 for (int i = 0; i < ws->children->length; ++i) {
224 struct sway_container *_child = ws->children->items[i];
225 container_move_to(new_parent, _child);
226 }
227
228 int index = move_offs(move_dir);
229 container_insert_child(ws, child, index < 0 ? 0 : 1);
230 ws->layout =
231 move_dir == MOVE_LEFT || move_dir == MOVE_RIGHT ? L_HORIZ : L_VERT;
232
233 container_flatten(ws);
234 container_reap_empty(original_parent);
235 container_create_notify(new_parent);
236}
237
238static void move_out_of_tabs_stacks(struct sway_container *container,
239 struct sway_container *current, enum movement_direction move_dir,
240 int offs) {
241 if (container->parent == current->parent
242 && current->parent->children->length == 1) {
243 wlr_log(WLR_DEBUG, "Changing layout of %zd", current->parent->id);
244 current->parent->layout = move_dir ==
245 MOVE_LEFT || move_dir == MOVE_RIGHT ? L_HORIZ : L_VERT;
246 return;
247 }
248
249 wlr_log(WLR_DEBUG, "Moving out of tab/stack into a split");
250 bool is_workspace = current->parent->type == C_WORKSPACE;
251 struct sway_container *new_parent = container_split(current->parent,
252 move_dir == MOVE_LEFT || move_dir == MOVE_RIGHT ? L_HORIZ : L_VERT);
253 if (is_workspace) {
254 container_insert_child(new_parent->parent, container, offs < 0 ? 0 : 1);
255 } else {
256 container_insert_child(new_parent, container, offs < 0 ? 0 : 1);
257 container_reap_empty(new_parent->parent);
258 container_flatten(new_parent->parent);
259 }
260 container_create_notify(new_parent);
261 container_notify_subtree_changed(new_parent);
262}
263
264static void container_move(struct sway_container *container,
265 enum movement_direction move_dir, int move_amt) {
266 if (!sway_assert(
267 container->type != C_CONTAINER || container->type != C_VIEW,
268 "Can only move containers and views")) {
269 return;
270 }
271 int offs = move_offs(move_dir);
272
273 struct sway_container *sibling = NULL;
274 struct sway_container *current = container;
275 struct sway_container *parent = current->parent;
276 struct sway_container *top = &root_container;
277
278 // If moving a fullscreen view, only consider outputs
279 if (container->is_fullscreen) {
280 current = container_parent(container, C_OUTPUT);
281 } else if (container_is_fullscreen_or_child(container) ||
282 container_is_floating_or_child(container)) {
283 // If we've fullscreened a split container, only allow the child to move
284 // around within the fullscreen parent.
285 // Same with floating a split container.
286 struct sway_container *ws = container_parent(container, C_WORKSPACE);
287 top = ws->sway_workspace->fullscreen;
288 }
289
290 struct sway_container *new_parent = container_flatten(parent);
291 if (new_parent != parent) {
292 // Special case: we were the last one in this container, so leave
293 return;
294 }
295
296 while (!sibling) {
297 if (current == top) {
298 return;
299 }
300
301 parent = current->parent;
302 wlr_log(WLR_DEBUG, "Visiting %p %s '%s'", current,
303 container_type_to_str(current->type), current->name);
304
305 int index = container_sibling_index(current);
306
307 switch (current->type) {
308 case C_OUTPUT: {
309 enum wlr_direction wlr_dir = 0;
310 if (!sway_assert(sway_dir_to_wlr(move_dir, &wlr_dir),
311 "got invalid direction: %d", move_dir)) {
312 return;
313 }
314 double ref_lx = current->x + current->width / 2;
315 double ref_ly = current->y + current->height / 2;
316 struct wlr_output *next = wlr_output_layout_adjacent_output(
317 root_container.sway_root->output_layout, wlr_dir,
318 current->sway_output->wlr_output, ref_lx, ref_ly);
319 if (!next) {
320 wlr_log(WLR_DEBUG, "Hit edge of output, nowhere else to go");
321 return;
322 }
323 struct sway_output *next_output = next->data;
324 current = next_output->swayc;
325 wlr_log(WLR_DEBUG, "Selected next output (%s)", current->name);
326 // Select workspace and get outta here
327 current = seat_get_focus_inactive(
328 config->handler_context.seat, current);
329 if (current->type != C_WORKSPACE) {
330 current = container_parent(current, C_WORKSPACE);
331 }
332 sibling = current;
49 break; 333 break;
50 } 334 }
335 case C_WORKSPACE:
336 if (!is_parallel(current->layout, move_dir)) {
337 if (current->children->length >= 2) {
338 wlr_log(WLR_DEBUG, "Rejiggering the workspace (%d kiddos)",
339 current->children->length);
340 workspace_rejigger(current, container, move_dir);
341 return;
342 } else {
343 wlr_log(WLR_DEBUG, "Selecting output");
344 current = current->parent;
345 }
346 } else if (current->layout == L_TABBED
347 || current->layout == L_STACKED) {
348 wlr_log(WLR_DEBUG, "Rejiggering out of tabs/stacks");
349 workspace_rejigger(current, container, move_dir);
350 } else {
351 wlr_log(WLR_DEBUG, "Selecting output");
352 current = current->parent;
353 }
354 break;
355 case C_CONTAINER:
356 case C_VIEW:
357 if (is_parallel(parent->layout, move_dir)) {
358 if ((index == parent->children->length - 1 && offs > 0)
359 || (index == 0 && offs < 0)) {
360 if (current->parent == container->parent) {
361 if (!parent->is_fullscreen &&
362 (parent->layout == L_TABBED ||
363 parent->layout == L_STACKED)) {
364 move_out_of_tabs_stacks(container, current,
365 move_dir, offs);
366 return;
367 } else {
368 wlr_log(WLR_DEBUG, "Hit limit, selecting parent");
369 current = current->parent;
370 }
371 } else {
372 wlr_log(WLR_DEBUG, "Hit limit, "
373 "promoting descendant to sibling");
374 // Special case
375 container_insert_child(current->parent, container,
376 index + (offs < 0 ? 0 : 1));
377 container->width = container->height = 0;
378 return;
379 }
380 } else {
381 sibling = parent->children->items[index + offs];
382 wlr_log(WLR_DEBUG, "Selecting sibling id:%zd", sibling->id);
383 }
384 } else if (!parent->is_fullscreen && (parent->layout == L_TABBED ||
385 parent->layout == L_STACKED)) {
386 move_out_of_tabs_stacks(container, current, move_dir, offs);
387 return;
388 } else {
389 wlr_log(WLR_DEBUG, "Moving up to find a parallel container");
390 current = current->parent;
391 }
392 break;
393 default:
394 sway_assert(0, "Not expecting to see container of type %s here",
395 container_type_to_str(current->type));
396 return;
397 }
398 }
399
400 // Part two: move stuff around
401 int index = container_sibling_index(container);
402 struct sway_container *old_parent = container->parent;
403
404 while (sibling) {
405 switch (sibling->type) {
406 case C_VIEW:
407 if (sibling->parent == container->parent) {
408 wlr_log(WLR_DEBUG, "Swapping siblings");
409 sibling->parent->children->items[index + offs] = container;
410 sibling->parent->children->items[index] = sibling;
411 } else {
412 wlr_log(WLR_DEBUG, "Promoting to sibling of cousin");
413 container_insert_child(sibling->parent, container,
414 container_sibling_index(sibling) + (offs > 0 ? 0 : 1));
415 container->width = container->height = 0;
416 }
417 sibling = NULL;
418 break;
419 case C_WORKSPACE: // Note: only in the case of moving between outputs
420 case C_CONTAINER:
421 if (is_parallel(sibling->layout, move_dir)) {
422 int limit = container_limit(sibling, invert_movement(move_dir));
423 wlr_log(WLR_DEBUG, "limit: %d", limit);
424 wlr_log(WLR_DEBUG,
425 "Reparenting container (parallel) to index %d "
426 "(move dir: %d)", limit, move_dir);
427 container_insert_child(sibling, container, limit);
428 container->width = container->height = 0;
429 sibling = NULL;
430 } else {
431 wlr_log(WLR_DEBUG, "Reparenting container (perpendicular)");
432 struct sway_container *focus_inactive = seat_get_focus_inactive(
433 config->handler_context.seat, sibling);
434 if (focus_inactive && focus_inactive != sibling) {
435 while (focus_inactive->parent != sibling) {
436 focus_inactive = focus_inactive->parent;
437 }
438 wlr_log(WLR_DEBUG, "Focus inactive: id:%zd",
439 focus_inactive->id);
440 sibling = focus_inactive;
441 continue;
442 } else if (sibling->children->length) {
443 wlr_log(WLR_DEBUG, "No focus-inactive, adding arbitrarily");
444 container_remove_child(container);
445 container_add_sibling(sibling->children->items[0], container);
446 } else {
447 wlr_log(WLR_DEBUG, "No kiddos, adding container alone");
448 container_remove_child(container);
449 container_add_child(sibling, container);
450 }
451 container->width = container->height = 0;
452 sibling = NULL;
453 }
454 break;
455 default:
456 sway_assert(0, "Not expecting to see container of type %s here",
457 container_type_to_str(sibling->type));
458 return;
459 }
460 }
461
462 container_notify_subtree_changed(old_parent);
463 container_notify_subtree_changed(container->parent);
464
465 if (container->type == C_VIEW) {
466 ipc_event_window(container, "move");
467 }
468
469 if (old_parent) {
470 seat_set_focus(config->handler_context.seat, old_parent);
471 seat_set_focus(config->handler_context.seat, container);
472 }
473
474 struct sway_container *last_ws = old_parent;
475 struct sway_container *next_ws = container->parent;
476 if (last_ws && last_ws->type != C_WORKSPACE) {
477 last_ws = container_parent(last_ws, C_WORKSPACE);
478 }
479 if (next_ws && next_ws->type != C_WORKSPACE) {
480 next_ws = container_parent(next_ws, C_WORKSPACE);
51 } 481 }
52 return output_by_name(direction); 482 if (last_ws && next_ws && last_ws != next_ws) {
483 ipc_event_workspace(last_ws, next_ws, "focus");
484 workspace_detect_urgent(last_ws);
485 workspace_detect_urgent(next_ws);
486 }
487 container_end_mouse_operation(container);
53} 488}
54 489
55static struct cmd_results *cmd_move_container(struct sway_container *current, 490static struct cmd_results *cmd_move_container(struct sway_container *current,
@@ -247,7 +682,7 @@ static void workspace_move_to_output(struct sway_container *workspace,
247 } 682 }
248 683
249 // Try to remove an empty workspace from the destination output. 684 // Try to remove an empty workspace from the destination output.
250 container_reap_empty_recursive(new_output_focus); 685 container_reap_empty(new_output_focus);
251 686
252 output_sort_workspaces(output); 687 output_sort_workspaces(output);
253 seat_set_focus(seat, output); 688 seat_set_focus(seat, output);
@@ -359,7 +794,8 @@ static struct cmd_results *move_in_direction(struct sway_container *container,
359 794
360static const char *expected_position_syntax = 795static const char *expected_position_syntax =
361 "Expected 'move [absolute] position <x> [px] <y> [px]' or " 796 "Expected 'move [absolute] position <x> [px] <y> [px]' or "
362 "'move [absolute] position center|mouse'"; 797 "'move [absolute] position center' or "
798 "'move position cursor|mouse|pointer'";
363 799
364static struct cmd_results *move_to_position(struct sway_container *container, 800static struct cmd_results *move_to_position(struct sway_container *container,
365 int argc, char **argv) { 801 int argc, char **argv) {
@@ -371,7 +807,10 @@ static struct cmd_results *move_to_position(struct sway_container *container,
371 if (!argc) { 807 if (!argc) {
372 return cmd_results_new(CMD_FAILURE, "move", expected_position_syntax); 808 return cmd_results_new(CMD_FAILURE, "move", expected_position_syntax);
373 } 809 }
810
811 bool absolute = false;
374 if (strcmp(argv[0], "absolute") == 0) { 812 if (strcmp(argv[0], "absolute") == 0) {
813 absolute = true;
375 --argc; 814 --argc;
376 ++argv; 815 ++argv;
377 } 816 }
@@ -385,7 +824,8 @@ static struct cmd_results *move_to_position(struct sway_container *container,
385 if (!argc) { 824 if (!argc) {
386 return cmd_results_new(CMD_FAILURE, "move", expected_position_syntax); 825 return cmd_results_new(CMD_FAILURE, "move", expected_position_syntax);
387 } 826 }
388 if (strcmp(argv[0], "mouse") == 0) { 827 if (strcmp(argv[0], "cursor") == 0 || strcmp(argv[0], "mouse") == 0 ||
828 strcmp(argv[0], "pointer") == 0) {
389 struct sway_seat *seat = config->handler_context.seat; 829 struct sway_seat *seat = config->handler_context.seat;
390 if (!seat->cursor) { 830 if (!seat->cursor) {
391 return cmd_results_new(CMD_FAILURE, "move", "No cursor device"); 831 return cmd_results_new(CMD_FAILURE, "move", "No cursor device");
@@ -395,9 +835,15 @@ static struct cmd_results *move_to_position(struct sway_container *container,
395 container_floating_move_to(container, lx, ly); 835 container_floating_move_to(container, lx, ly);
396 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 836 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
397 } else if (strcmp(argv[0], "center") == 0) { 837 } else if (strcmp(argv[0], "center") == 0) {
398 struct sway_container *ws = container_parent(container, C_WORKSPACE); 838 double lx, ly;
399 double lx = ws->x + (ws->width - container->width) / 2; 839 if (absolute) {
400 double ly = ws->y + (ws->height - container->height) / 2; 840 lx = root_container.x + (root_container.width - container->width) / 2;
841 ly = root_container.y + (root_container.height - container->height) / 2;
842 } else {
843 struct sway_container *ws = container_parent(container, C_WORKSPACE);
844 lx = ws->x + (ws->width - container->width) / 2;
845 ly = ws->y + (ws->height - container->height) / 2;
846 }
401 container_floating_move_to(container, lx, ly); 847 container_floating_move_to(container, lx, ly);
402 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 848 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
403 } 849 }
@@ -429,6 +875,11 @@ static struct cmd_results *move_to_position(struct sway_container *container,
429 "Invalid position specified"); 875 "Invalid position specified");
430 } 876 }
431 877
878 if (!absolute) {
879 struct sway_container *ws = container_parent(container, C_WORKSPACE);
880 lx += ws->x;
881 ly += ws->y;
882 }
432 container_floating_move_to(container, lx, ly); 883 container_floating_move_to(container, lx, ly);
433 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 884 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
434} 885}
diff --git a/sway/commands/output.c b/sway/commands/output.c
index ef1b7a69..00910843 100644
--- a/sway/commands/output.c
+++ b/sway/commands/output.c
@@ -1,7 +1,6 @@
1#include "sway/commands.h" 1#include "sway/commands.h"
2#include "sway/config.h" 2#include "sway/config.h"
3#include "sway/output.h" 3#include "sway/output.h"
4#include "sway/tree/layout.h"
5#include "list.h" 4#include "list.h"
6#include "log.h" 5#include "log.h"
7 6
diff --git a/sway/commands/resize.c b/sway/commands/resize.c
index ea1e36ff..ad659ef5 100644
--- a/sway/commands/resize.c
+++ b/sway/commands/resize.c
@@ -159,6 +159,27 @@ static int parallel_size(struct sway_container *c, enum resize_axis a) {
159 return normalize_axis(a) == RESIZE_AXIS_HORIZONTAL ? c->width : c->height; 159 return normalize_axis(a) == RESIZE_AXIS_HORIZONTAL ? c->width : c->height;
160} 160}
161 161
162static void container_recursive_resize(struct sway_container *container,
163 double amount, enum wlr_edges edge) {
164 bool layout_match = true;
165 wlr_log(WLR_DEBUG, "Resizing %p with amount: %f", container, amount);
166 if (edge == WLR_EDGE_LEFT || edge == WLR_EDGE_RIGHT) {
167 container->width += amount;
168 layout_match = container->layout == L_HORIZ;
169 } else if (edge == WLR_EDGE_TOP || edge == WLR_EDGE_BOTTOM) {
170 container->height += amount;
171 layout_match = container->layout == L_VERT;
172 }
173 if (container->children) {
174 for (int i = 0; i < container->children->length; i++) {
175 struct sway_container *child = container->children->items[i];
176 double amt = layout_match ?
177 amount / container->children->length : amount;
178 container_recursive_resize(child, amt, edge);
179 }
180 }
181}
182
162static void resize_tiled(struct sway_container *parent, int amount, 183static void resize_tiled(struct sway_container *parent, int amount,
163 enum resize_axis axis) { 184 enum resize_axis axis) {
164 struct sway_container *focused = parent; 185 struct sway_container *focused = parent;
diff --git a/sway/commands/show_marks.c b/sway/commands/show_marks.c
index dd7d170c..1844e917 100644
--- a/sway/commands/show_marks.c
+++ b/sway/commands/show_marks.c
@@ -2,6 +2,7 @@
2#include <string.h> 2#include <string.h>
3#include "sway/commands.h" 3#include "sway/commands.h"
4#include "sway/config.h" 4#include "sway/config.h"
5#include "sway/tree/root.h"
5#include "sway/tree/view.h" 6#include "sway/tree/view.h"
6#include "sway/output.h" 7#include "sway/output.h"
7#include "list.h" 8#include "list.h"
diff --git a/sway/commands/sticky.c b/sway/commands/sticky.c
index a0dd7215..8692e08d 100644
--- a/sway/commands/sticky.c
+++ b/sway/commands/sticky.c
@@ -6,8 +6,8 @@
6#include "sway/output.h" 6#include "sway/output.h"
7#include "sway/tree/arrange.h" 7#include "sway/tree/arrange.h"
8#include "sway/tree/container.h" 8#include "sway/tree/container.h"
9#include "sway/tree/layout.h"
10#include "sway/tree/view.h" 9#include "sway/tree/view.h"
10#include "sway/tree/workspace.h"
11#include "list.h" 11#include "list.h"
12 12
13struct cmd_results *cmd_sticky(int argc, char **argv) { 13struct cmd_results *cmd_sticky(int argc, char **argv) {
@@ -44,7 +44,9 @@ struct cmd_results *cmd_sticky(int argc, char **argv) {
44 struct sway_container *focused_workspace = container_parent(focus, C_WORKSPACE); 44 struct sway_container *focused_workspace = container_parent(focus, C_WORKSPACE);
45 struct sway_container *current_workspace = container_parent(container, C_WORKSPACE); 45 struct sway_container *current_workspace = container_parent(container, C_WORKSPACE);
46 if (current_workspace != focused_workspace) { 46 if (current_workspace != focused_workspace) {
47 container_move_to(container, focused_workspace); 47 container_remove_child(container);
48 workspace_add_floating(focused_workspace, container);
49 container_handle_fullscreen_reparent(container, current_workspace);
48 arrange_windows(focused_workspace); 50 arrange_windows(focused_workspace);
49 if (!container_reap_empty(current_workspace)) { 51 if (!container_reap_empty(current_workspace)) {
50 arrange_windows(current_workspace); 52 arrange_windows(current_workspace);
diff --git a/sway/commands/swap.c b/sway/commands/swap.c
index f881a002..f25c43a1 100644
--- a/sway/commands/swap.c
+++ b/sway/commands/swap.c
@@ -1,15 +1,141 @@
1#define _POSIX_C_SOURCE 200809L
1#include <strings.h> 2#include <strings.h>
2#include <wlr/util/log.h> 3#include <wlr/util/log.h>
3#include "config.h" 4#include "config.h"
5#include "log.h"
4#include "sway/commands.h" 6#include "sway/commands.h"
5#include "sway/tree/arrange.h" 7#include "sway/tree/arrange.h"
6#include "sway/tree/layout.h" 8#include "sway/tree/root.h"
7#include "sway/tree/view.h" 9#include "sway/tree/view.h"
10#include "sway/tree/workspace.h"
8#include "stringop.h" 11#include "stringop.h"
9 12
10static const char* EXPECTED_SYNTAX = 13static const char* EXPECTED_SYNTAX =
11 "Expected 'swap container with id|con_id|mark <arg>'"; 14 "Expected 'swap container with id|con_id|mark <arg>'";
12 15
16static void swap_places(struct sway_container *con1,
17 struct sway_container *con2) {
18 struct sway_container *temp = malloc(sizeof(struct sway_container));
19 temp->x = con1->x;
20 temp->y = con1->y;
21 temp->width = con1->width;
22 temp->height = con1->height;
23 temp->parent = con1->parent;
24
25 con1->x = con2->x;
26 con1->y = con2->y;
27 con1->width = con2->width;
28 con1->height = con2->height;
29
30 con2->x = temp->x;
31 con2->y = temp->y;
32 con2->width = temp->width;
33 con2->height = temp->height;
34
35 int temp_index = container_sibling_index(con1);
36 container_insert_child(con2->parent, con1, container_sibling_index(con2));
37 container_insert_child(temp->parent, con2, temp_index);
38
39 free(temp);
40}
41
42static void swap_focus(struct sway_container *con1,
43 struct sway_container *con2, struct sway_seat *seat,
44 struct sway_container *focus) {
45 if (focus == con1 || focus == con2) {
46 struct sway_container *ws1 = container_parent(con1, C_WORKSPACE);
47 struct sway_container *ws2 = container_parent(con2, C_WORKSPACE);
48 if (focus == con1 && (con2->parent->layout == L_TABBED
49 || con2->parent->layout == L_STACKED)) {
50 if (workspace_is_visible(ws2)) {
51 seat_set_focus_warp(seat, con2, false, true);
52 }
53 seat_set_focus(seat, ws1 != ws2 ? con2 : con1);
54 } else if (focus == con2 && (con1->parent->layout == L_TABBED
55 || con1->parent->layout == L_STACKED)) {
56 if (workspace_is_visible(ws1)) {
57 seat_set_focus_warp(seat, con1, false, true);
58 }
59 seat_set_focus(seat, ws1 != ws2 ? con1 : con2);
60 } else if (ws1 != ws2) {
61 seat_set_focus(seat, focus == con1 ? con2 : con1);
62 } else {
63 seat_set_focus(seat, focus);
64 }
65 } else {
66 seat_set_focus(seat, focus);
67 }
68}
69
70static void container_swap(struct sway_container *con1,
71 struct sway_container *con2) {
72 if (!sway_assert(con1 && con2, "Cannot swap with nothing")) {
73 return;
74 }
75 if (!sway_assert(con1->type >= C_CONTAINER && con2->type >= C_CONTAINER,
76 "Can only swap containers and views")) {
77 return;
78 }
79 if (!sway_assert(!container_has_ancestor(con1, con2)
80 && !container_has_ancestor(con2, con1),
81 "Cannot swap ancestor and descendant")) {
82 return;
83 }
84 if (!sway_assert(!container_is_floating(con1)
85 && !container_is_floating(con2),
86 "Swapping with floating containers is not supported")) {
87 return;
88 }
89
90 wlr_log(WLR_DEBUG, "Swapping containers %zu and %zu", con1->id, con2->id);
91
92 int fs1 = con1->is_fullscreen;
93 int fs2 = con2->is_fullscreen;
94 if (fs1) {
95 container_set_fullscreen(con1, false);
96 }
97 if (fs2) {
98 container_set_fullscreen(con2, false);
99 }
100
101 struct sway_seat *seat = input_manager_get_default_seat(input_manager);
102 struct sway_container *focus = seat_get_focus(seat);
103 struct sway_container *vis1 = container_parent(
104 seat_get_focus_inactive(seat, container_parent(con1, C_OUTPUT)),
105 C_WORKSPACE);
106 struct sway_container *vis2 = container_parent(
107 seat_get_focus_inactive(seat, container_parent(con2, C_OUTPUT)),
108 C_WORKSPACE);
109
110 char *stored_prev_name = NULL;
111 if (prev_workspace_name) {
112 stored_prev_name = strdup(prev_workspace_name);
113 }
114
115 swap_places(con1, con2);
116
117 if (!workspace_is_visible(vis1)) {
118 seat_set_focus(seat, seat_get_focus_inactive(seat, vis1));
119 }
120 if (!workspace_is_visible(vis2)) {
121 seat_set_focus(seat, seat_get_focus_inactive(seat, vis2));
122 }
123
124 swap_focus(con1, con2, seat, focus);
125
126 if (stored_prev_name) {
127 free(prev_workspace_name);
128 prev_workspace_name = stored_prev_name;
129 }
130
131 if (fs1) {
132 container_set_fullscreen(con2, true);
133 }
134 if (fs2) {
135 container_set_fullscreen(con1, true);
136 }
137}
138
13static bool test_con_id(struct sway_container *container, void *con_id) { 139static bool test_con_id(struct sway_container *container, void *con_id) {
14 return container->id == (size_t)con_id; 140 return container->id == (size_t)con_id;
15} 141}
diff --git a/sway/commands/unmark.c b/sway/commands/unmark.c
index c183785b..62127c97 100644
--- a/sway/commands/unmark.c
+++ b/sway/commands/unmark.c
@@ -2,6 +2,7 @@
2#include <string.h> 2#include <string.h>
3#include "sway/commands.h" 3#include "sway/commands.h"
4#include "sway/config.h" 4#include "sway/config.h"
5#include "sway/tree/root.h"
5#include "sway/tree/view.h" 6#include "sway/tree/view.h"
6#include "list.h" 7#include "list.h"
7#include "log.h" 8#include "log.h"
diff --git a/sway/commands/urgent.c b/sway/commands/urgent.c
index 51c497c4..bccb33fe 100644
--- a/sway/commands/urgent.c
+++ b/sway/commands/urgent.c
@@ -4,7 +4,6 @@
4#include "sway/tree/arrange.h" 4#include "sway/tree/arrange.h"
5#include "sway/tree/container.h" 5#include "sway/tree/container.h"
6#include "sway/tree/view.h" 6#include "sway/tree/view.h"
7#include "sway/tree/layout.h"
8#include "util.h" 7#include "util.h"
9 8
10struct cmd_results *cmd_urgent(int argc, char **argv) { 9struct cmd_results *cmd_urgent(int argc, char **argv) {
diff --git a/sway/config.c b/sway/config.c
index 642abbac..8105722a 100644
--- a/sway/config.c
+++ b/sway/config.c
@@ -27,7 +27,7 @@
27#include "sway/criteria.h" 27#include "sway/criteria.h"
28#include "sway/swaynag.h" 28#include "sway/swaynag.h"
29#include "sway/tree/arrange.h" 29#include "sway/tree/arrange.h"
30#include "sway/tree/layout.h" 30#include "sway/tree/root.h"
31#include "sway/tree/workspace.h" 31#include "sway/tree/workspace.h"
32#include "cairo.h" 32#include "cairo.h"
33#include "pango.h" 33#include "pango.h"
diff --git a/sway/config/output.c b/sway/config/output.c
index 1d8cb3ef..16ec9339 100644
--- a/sway/config/output.c
+++ b/sway/config/output.c
@@ -9,6 +9,7 @@
9#include <wlr/types/wlr_output_layout.h> 9#include <wlr/types/wlr_output_layout.h>
10#include "sway/config.h" 10#include "sway/config.h"
11#include "sway/output.h" 11#include "sway/output.h"
12#include "sway/tree/root.h"
12#include "log.h" 13#include "log.h"
13 14
14int output_name_cmp(const void *item, const void *data) { 15int output_name_cmp(const void *item, const void *data) {
@@ -181,13 +182,11 @@ void apply_output_config(struct output_config *oc, struct sway_container *output
181 struct wlr_output *wlr_output = output->sway_output->wlr_output; 182 struct wlr_output *wlr_output = output->sway_output->wlr_output;
182 183
183 if (oc && oc->enabled == 0) { 184 if (oc && oc->enabled == 0) {
184 struct sway_output *sway_output = output->sway_output;
185 if (output->sway_output->bg_pid != 0) { 185 if (output->sway_output->bg_pid != 0) {
186 terminate_swaybg(output->sway_output->bg_pid); 186 terminate_swaybg(output->sway_output->bg_pid);
187 output->sway_output->bg_pid = 0; 187 output->sway_output->bg_pid = 0;
188 } 188 }
189 container_destroy(output); 189 output_begin_destroy(output);
190 sway_output->swayc = NULL;
191 wlr_output_layout_remove(root_container.sway_root->output_layout, 190 wlr_output_layout_remove(root_container.sway_root->output_layout,
192 wlr_output); 191 wlr_output);
193 return; 192 return;
@@ -290,7 +289,8 @@ void apply_output_config_to_outputs(struct output_config *oc) {
290 bool wildcard = strcmp(oc->name, "*") == 0; 289 bool wildcard = strcmp(oc->name, "*") == 0;
291 char id[128]; 290 char id[128];
292 struct sway_output *sway_output; 291 struct sway_output *sway_output;
293 wl_list_for_each(sway_output, &root_container.sway_root->outputs, link) { 292 wl_list_for_each(sway_output,
293 &root_container.sway_root->all_outputs, link) {
294 char *name = sway_output->wlr_output->name; 294 char *name = sway_output->wlr_output->name;
295 output_get_identifier(id, sizeof(id), sway_output); 295 output_get_identifier(id, sizeof(id), sway_output);
296 if (wildcard || !strcmp(name, oc->name) || !strcmp(id, oc->name)) { 296 if (wildcard || !strcmp(name, oc->name) || !strcmp(id, oc->name)) {
@@ -350,7 +350,8 @@ static void default_output_config(struct output_config *oc,
350 350
351void create_default_output_configs(void) { 351void create_default_output_configs(void) {
352 struct sway_output *sway_output; 352 struct sway_output *sway_output;
353 wl_list_for_each(sway_output, &root_container.sway_root->outputs, link) { 353 wl_list_for_each(sway_output,
354 &root_container.sway_root->all_outputs, link) {
354 char *name = sway_output->wlr_output->name; 355 char *name = sway_output->wlr_output->name;
355 struct output_config *oc = new_output_config(name); 356 struct output_config *oc = new_output_config(name);
356 default_output_config(oc, sway_output->wlr_output); 357 default_output_config(oc, sway_output->wlr_output);
diff --git a/sway/criteria.c b/sway/criteria.c
index 81c2325a..5452c4ee 100644
--- a/sway/criteria.c
+++ b/sway/criteria.c
@@ -6,6 +6,7 @@
6#include "sway/criteria.h" 6#include "sway/criteria.h"
7#include "sway/tree/container.h" 7#include "sway/tree/container.h"
8#include "sway/config.h" 8#include "sway/config.h"
9#include "sway/tree/root.h"
9#include "sway/tree/view.h" 10#include "sway/tree/view.h"
10#include "stringop.h" 11#include "stringop.h"
11#include "list.h" 12#include "list.h"
diff --git a/sway/debug-tree.c b/sway/debug-tree.c
index ea0826b9..2768cf58 100644
--- a/sway/debug-tree.c
+++ b/sway/debug-tree.c
@@ -9,7 +9,7 @@
9#include "sway/output.h" 9#include "sway/output.h"
10#include "sway/server.h" 10#include "sway/server.h"
11#include "sway/tree/container.h" 11#include "sway/tree/container.h"
12#include "sway/tree/layout.h" 12#include "sway/tree/root.h"
13#include "cairo.h" 13#include "cairo.h"
14#include "config.h" 14#include "config.h"
15#include "pango.h" 15#include "pango.h"
diff --git a/sway/desktop/layer_shell.c b/sway/desktop/layer_shell.c
index a2935883..1fae5db2 100644
--- a/sway/desktop/layer_shell.c
+++ b/sway/desktop/layer_shell.c
@@ -14,7 +14,6 @@
14#include "sway/output.h" 14#include "sway/output.h"
15#include "sway/server.h" 15#include "sway/server.h"
16#include "sway/tree/arrange.h" 16#include "sway/tree/arrange.h"
17#include "sway/tree/layout.h"
18#include "log.h" 17#include "log.h"
19 18
20static void apply_exclusive(struct wlr_box *usable_area, 19static void apply_exclusive(struct wlr_box *usable_area,
diff --git a/sway/desktop/output.c b/sway/desktop/output.c
index 3d8bbff5..c228979d 100644
--- a/sway/desktop/output.c
+++ b/sway/desktop/output.c
@@ -16,6 +16,7 @@
16#include "log.h" 16#include "log.h"
17#include "config.h" 17#include "config.h"
18#include "sway/config.h" 18#include "sway/config.h"
19#include "sway/desktop/transaction.h"
19#include "sway/input/input-manager.h" 20#include "sway/input/input-manager.h"
20#include "sway/input/seat.h" 21#include "sway/input/seat.h"
21#include "sway/layers.h" 22#include "sway/layers.h"
@@ -23,7 +24,7 @@
23#include "sway/server.h" 24#include "sway/server.h"
24#include "sway/tree/arrange.h" 25#include "sway/tree/arrange.h"
25#include "sway/tree/container.h" 26#include "sway/tree/container.h"
26#include "sway/tree/layout.h" 27#include "sway/tree/root.h"
27#include "sway/tree/view.h" 28#include "sway/tree/view.h"
28#include "sway/tree/workspace.h" 29#include "sway/tree/workspace.h"
29 30
@@ -498,7 +499,7 @@ void output_damage_whole_container(struct sway_output *output,
498static void damage_handle_destroy(struct wl_listener *listener, void *data) { 499static void damage_handle_destroy(struct wl_listener *listener, void *data) {
499 struct sway_output *output = 500 struct sway_output *output =
500 wl_container_of(listener, output, damage_destroy); 501 wl_container_of(listener, output, damage_destroy);
501 container_destroy(output->swayc); 502 output_begin_destroy(output->swayc);
502} 503}
503 504
504static void handle_destroy(struct wl_listener *listener, void *data) { 505static void handle_destroy(struct wl_listener *listener, void *data) {
@@ -506,7 +507,7 @@ static void handle_destroy(struct wl_listener *listener, void *data) {
506 wl_signal_emit(&output->events.destroy, output); 507 wl_signal_emit(&output->events.destroy, output);
507 508
508 if (output->swayc) { 509 if (output->swayc) {
509 container_destroy(output->swayc); 510 output_begin_destroy(output->swayc);
510 } 511 }
511 512
512 wl_list_remove(&output->link); 513 wl_list_remove(&output->link);
@@ -556,7 +557,7 @@ void handle_new_output(struct wl_listener *listener, void *data) {
556 wl_signal_add(&wlr_output->events.destroy, &output->destroy); 557 wl_signal_add(&wlr_output->events.destroy, &output->destroy);
557 output->destroy.notify = handle_destroy; 558 output->destroy.notify = handle_destroy;
558 559
559 wl_list_insert(&root_container.sway_root->outputs, &output->link); 560 wl_list_insert(&root_container.sway_root->all_outputs, &output->link);
560 561
561 if (!wl_list_empty(&wlr_output->modes)) { 562 if (!wl_list_empty(&wlr_output->modes)) {
562 struct wlr_output_mode *mode = 563 struct wlr_output_mode *mode =
diff --git a/sway/desktop/render.c b/sway/desktop/render.c
index 5cf8abc0..5556e5b3 100644
--- a/sway/desktop/render.c
+++ b/sway/desktop/render.c
@@ -24,7 +24,7 @@
24#include "sway/server.h" 24#include "sway/server.h"
25#include "sway/tree/arrange.h" 25#include "sway/tree/arrange.h"
26#include "sway/tree/container.h" 26#include "sway/tree/container.h"
27#include "sway/tree/layout.h" 27#include "sway/tree/root.h"
28#include "sway/tree/view.h" 28#include "sway/tree/view.h"
29#include "sway/tree/workspace.h" 29#include "sway/tree/workspace.h"
30 30
@@ -619,9 +619,7 @@ static void render_container_tabbed(struct sway_output *output,
619 struct sway_container *current = pstate->focused_inactive_child; 619 struct sway_container *current = pstate->focused_inactive_child;
620 struct border_colors *current_colors = &config->border_colors.unfocused; 620 struct border_colors *current_colors = &config->border_colors.unfocused;
621 621
622 double width_gap_adjustment = 2 * pstate->current_gaps; 622 int tab_width = (pstate->swayc_width) / pstate->children->length;
623 int tab_width =
624 (pstate->swayc_width - width_gap_adjustment) / pstate->children->length;
625 623
626 // Render tabs 624 // Render tabs
627 for (int i = 0; i < pstate->children->length; ++i) { 625 for (int i = 0; i < pstate->children->length; ++i) {
@@ -656,11 +654,10 @@ static void render_container_tabbed(struct sway_output *output,
656 654
657 // Make last tab use the remaining width of the parent 655 // Make last tab use the remaining width of the parent
658 if (i == pstate->children->length - 1) { 656 if (i == pstate->children->length - 1) {
659 tab_width = 657 tab_width = pstate->swayc_width - tab_width * i;
660 pstate->swayc_width - width_gap_adjustment - tab_width * i;
661 } 658 }
662 659
663 render_titlebar(output, damage, child, x, cstate->swayc_y, tab_width, 660 render_titlebar(output, damage, child, x, pstate->swayc_y, tab_width,
664 colors, title_texture, marks_texture); 661 colors, title_texture, marks_texture);
665 662
666 if (child == current) { 663 if (child == current) {
@@ -721,9 +718,9 @@ static void render_container_stacked(struct sway_output *output,
721 marks_texture = view ? view->marks_unfocused : NULL; 718 marks_texture = view ? view->marks_unfocused : NULL;
722 } 719 }
723 720
724 int y = cstate->swayc_y + titlebar_height * i; 721 int y = pstate->swayc_y + titlebar_height * i;
725 render_titlebar(output, damage, child, cstate->swayc_x, y, 722 render_titlebar(output, damage, child, pstate->swayc_x, y,
726 cstate->swayc_width, colors, title_texture, marks_texture); 723 pstate->swayc_width, colors, title_texture, marks_texture);
727 724
728 if (child == current) { 725 if (child == current) {
729 current_colors = colors; 726 current_colors = colors;
diff --git a/sway/desktop/transaction.c b/sway/desktop/transaction.c
index f82e5ef2..c18529fb 100644
--- a/sway/desktop/transaction.c
+++ b/sway/desktop/transaction.c
@@ -54,7 +54,22 @@ static void transaction_destroy(struct sway_transaction *transaction) {
54 con->instruction = NULL; 54 con->instruction = NULL;
55 } 55 }
56 if (con->destroying && con->ntxnrefs == 0) { 56 if (con->destroying && con->ntxnrefs == 0) {
57 container_free(con); 57 switch (con->type) {
58 case C_ROOT:
59 break;
60 case C_OUTPUT:
61 output_destroy(con);
62 break;
63 case C_WORKSPACE:
64 workspace_destroy(con);
65 break;
66 case C_CONTAINER:
67 case C_VIEW:
68 container_destroy(con);
69 break;
70 case C_TYPES:
71 break;
72 }
58 } 73 }
59 free(instruction); 74 free(instruction);
60 } 75 }
diff --git a/sway/desktop/xdg_shell.c b/sway/desktop/xdg_shell.c
index aae129bd..587deb0f 100644
--- a/sway/desktop/xdg_shell.c
+++ b/sway/desktop/xdg_shell.c
@@ -8,12 +8,12 @@
8#include "log.h" 8#include "log.h"
9#include "sway/decoration.h" 9#include "sway/decoration.h"
10#include "sway/desktop.h" 10#include "sway/desktop.h"
11#include "sway/desktop/transaction.h"
11#include "sway/input/input-manager.h" 12#include "sway/input/input-manager.h"
12#include "sway/input/seat.h" 13#include "sway/input/seat.h"
13#include "sway/server.h" 14#include "sway/server.h"
14#include "sway/tree/arrange.h" 15#include "sway/tree/arrange.h"
15#include "sway/tree/container.h" 16#include "sway/tree/container.h"
16#include "sway/tree/layout.h"
17#include "sway/tree/view.h" 17#include "sway/tree/view.h"
18 18
19static const struct sway_view_child_impl popup_impl; 19static const struct sway_view_child_impl popup_impl;
@@ -448,7 +448,7 @@ static void handle_destroy(struct wl_listener *listener, void *data) {
448 wl_list_remove(&xdg_shell_view->map.link); 448 wl_list_remove(&xdg_shell_view->map.link);
449 wl_list_remove(&xdg_shell_view->unmap.link); 449 wl_list_remove(&xdg_shell_view->unmap.link);
450 view->wlr_xdg_surface = NULL; 450 view->wlr_xdg_surface = NULL;
451 view_destroy(view); 451 view_begin_destroy(view);
452} 452}
453 453
454struct sway_view *view_from_wlr_xdg_surface( 454struct sway_view *view_from_wlr_xdg_surface(
diff --git a/sway/desktop/xdg_shell_v6.c b/sway/desktop/xdg_shell_v6.c
index 277c53a3..175416f3 100644
--- a/sway/desktop/xdg_shell_v6.c
+++ b/sway/desktop/xdg_shell_v6.c
@@ -7,12 +7,12 @@
7#include "log.h" 7#include "log.h"
8#include "sway/decoration.h" 8#include "sway/decoration.h"
9#include "sway/desktop.h" 9#include "sway/desktop.h"
10#include "sway/desktop/transaction.h"
10#include "sway/input/input-manager.h" 11#include "sway/input/input-manager.h"
11#include "sway/input/seat.h" 12#include "sway/input/seat.h"
12#include "sway/server.h" 13#include "sway/server.h"
13#include "sway/tree/arrange.h" 14#include "sway/tree/arrange.h"
14#include "sway/tree/container.h" 15#include "sway/tree/container.h"
15#include "sway/tree/layout.h"
16#include "sway/tree/view.h" 16#include "sway/tree/view.h"
17 17
18static const struct sway_view_child_impl popup_impl; 18static const struct sway_view_child_impl popup_impl;
@@ -441,7 +441,7 @@ static void handle_destroy(struct wl_listener *listener, void *data) {
441 wl_list_remove(&xdg_shell_v6_view->map.link); 441 wl_list_remove(&xdg_shell_v6_view->map.link);
442 wl_list_remove(&xdg_shell_v6_view->unmap.link); 442 wl_list_remove(&xdg_shell_v6_view->unmap.link);
443 view->wlr_xdg_surface_v6 = NULL; 443 view->wlr_xdg_surface_v6 = NULL;
444 view_destroy(view); 444 view_begin_destroy(view);
445} 445}
446 446
447struct sway_view *view_from_wlr_xdg_surface_v6( 447struct sway_view *view_from_wlr_xdg_surface_v6(
diff --git a/sway/desktop/xwayland.c b/sway/desktop/xwayland.c
index ce7235e4..4e401008 100644
--- a/sway/desktop/xwayland.c
+++ b/sway/desktop/xwayland.c
@@ -14,7 +14,6 @@
14#include "sway/server.h" 14#include "sway/server.h"
15#include "sway/tree/arrange.h" 15#include "sway/tree/arrange.h"
16#include "sway/tree/container.h" 16#include "sway/tree/container.h"
17#include "sway/tree/layout.h"
18#include "sway/tree/view.h" 17#include "sway/tree/view.h"
19 18
20static const char *atom_map[ATOM_LAST] = { 19static const char *atom_map[ATOM_LAST] = {
@@ -341,7 +340,7 @@ static void handle_destroy(struct wl_listener *listener, void *data) {
341 wl_list_remove(&xwayland_view->set_hints.link); 340 wl_list_remove(&xwayland_view->set_hints.link);
342 wl_list_remove(&xwayland_view->map.link); 341 wl_list_remove(&xwayland_view->map.link);
343 wl_list_remove(&xwayland_view->unmap.link); 342 wl_list_remove(&xwayland_view->unmap.link);
344 view_destroy(&xwayland_view->view); 343 view_begin_destroy(&xwayland_view->view);
345} 344}
346 345
347static void handle_unmap(struct wl_listener *listener, void *data) { 346static void handle_unmap(struct wl_listener *listener, void *data) {
diff --git a/sway/input/cursor.c b/sway/input/cursor.c
index ba5e0400..00240e84 100644
--- a/sway/input/cursor.c
+++ b/sway/input/cursor.c
@@ -20,6 +20,7 @@
20#include "sway/layers.h" 20#include "sway/layers.h"
21#include "sway/output.h" 21#include "sway/output.h"
22#include "sway/tree/arrange.h" 22#include "sway/tree/arrange.h"
23#include "sway/tree/root.h"
23#include "sway/tree/view.h" 24#include "sway/tree/view.h"
24#include "sway/tree/workspace.h" 25#include "sway/tree/workspace.h"
25#include "wlr-layer-shell-unstable-v1-protocol.h" 26#include "wlr-layer-shell-unstable-v1-protocol.h"
diff --git a/sway/input/seat.c b/sway/input/seat.c
index 4077a8dd..36e1d232 100644
--- a/sway/input/seat.c
+++ b/sway/input/seat.c
@@ -25,10 +25,9 @@
25#include "sway/output.h" 25#include "sway/output.h"
26#include "sway/tree/arrange.h" 26#include "sway/tree/arrange.h"
27#include "sway/tree/container.h" 27#include "sway/tree/container.h"
28#include "sway/tree/container.h" 28#include "sway/tree/root.h"
29#include "sway/tree/view.h" 29#include "sway/tree/view.h"
30#include "sway/tree/workspace.h" 30#include "sway/tree/workspace.h"
31#include "sway/tree/workspace.h"
32 31
33static void seat_device_destroy(struct sway_seat_device *seat_device) { 32static void seat_device_destroy(struct sway_seat_device *seat_device) {
34 if (!seat_device) { 33 if (!seat_device) {
@@ -594,7 +593,7 @@ void seat_set_focus_warp(struct sway_seat *seat,
594 } 593 }
595 594
596 struct sway_container *last_focus = seat_get_focus(seat); 595 struct sway_container *last_focus = seat_get_focus(seat);
597 if (container && last_focus == container) { 596 if (last_focus == container) {
598 return; 597 return;
599 } 598 }
600 599
@@ -602,14 +601,26 @@ void seat_set_focus_warp(struct sway_seat *seat,
602 if (last_workspace && last_workspace->type != C_WORKSPACE) { 601 if (last_workspace && last_workspace->type != C_WORKSPACE) {
603 last_workspace = container_parent(last_workspace, C_WORKSPACE); 602 last_workspace = container_parent(last_workspace, C_WORKSPACE);
604 } 603 }
604
605 if (container == NULL) {
606 // Close any popups on the old focus
607 if (last_focus->type == C_VIEW) {
608 view_close_popups(last_focus->sway_view);
609 }
610 seat_send_unfocus(last_focus, seat);
611 seat->has_focus = false;
612 update_debug_tree();
613 return;
614 }
615
605 struct sway_container *new_workspace = container; 616 struct sway_container *new_workspace = container;
606 if (new_workspace && new_workspace->type != C_WORKSPACE) { 617 if (new_workspace->type != C_WORKSPACE) {
607 new_workspace = container_parent(new_workspace, C_WORKSPACE); 618 new_workspace = container_parent(new_workspace, C_WORKSPACE);
608 } 619 }
609 620
610 if (last_workspace && last_workspace == new_workspace 621 if (last_workspace == new_workspace
611 && last_workspace->sway_workspace->fullscreen 622 && last_workspace->sway_workspace->fullscreen
612 && container && !container_is_fullscreen_or_child(container)) { 623 && !container_is_fullscreen_or_child(container)) {
613 return; 624 return;
614 } 625 }
615 626
@@ -618,17 +629,17 @@ void seat_set_focus_warp(struct sway_seat *seat,
618 last_output = container_parent(last_output, C_OUTPUT); 629 last_output = container_parent(last_output, C_OUTPUT);
619 } 630 }
620 struct sway_container *new_output = container; 631 struct sway_container *new_output = container;
621 if (new_output && new_output->type != C_OUTPUT) { 632 if (new_output->type != C_OUTPUT) {
622 new_output = container_parent(new_output, C_OUTPUT); 633 new_output = container_parent(new_output, C_OUTPUT);
623 } 634 }
624 635
625 // find new output's old workspace, which might have to be removed if empty 636 // find new output's old workspace, which might have to be removed if empty
626 struct sway_container *new_output_last_ws = NULL; 637 struct sway_container *new_output_last_ws = NULL;
627 if (last_output && new_output && last_output != new_output) { 638 if (last_output != new_output) {
628 new_output_last_ws = seat_get_active_child(seat, new_output); 639 new_output_last_ws = seat_get_active_child(seat, new_output);
629 } 640 }
630 641
631 if (container && container->parent) { 642 if (container->parent) {
632 struct sway_seat_container *seat_con = 643 struct sway_seat_container *seat_con =
633 seat_container_from_container(seat, container); 644 seat_container_from_container(seat, container);
634 if (seat_con == NULL) { 645 if (seat_con == NULL) {
@@ -643,8 +654,7 @@ void seat_set_focus_warp(struct sway_seat *seat,
643 wl_list_insert(&seat->focus_stack, &parent->link); 654 wl_list_insert(&seat->focus_stack, &parent->link);
644 container_set_dirty(parent->container); 655 container_set_dirty(parent->container);
645 656
646 parent = 657 parent = seat_container_from_container(seat,
647 seat_container_from_container(seat,
648 parent->container->parent); 658 parent->container->parent);
649 } 659 }
650 660
@@ -653,19 +663,33 @@ void seat_set_focus_warp(struct sway_seat *seat,
653 663
654 if (last_focus) { 664 if (last_focus) {
655 seat_send_unfocus(last_focus, seat); 665 seat_send_unfocus(last_focus, seat);
666 container_set_dirty(last_focus);
656 } 667 }
657 seat_send_focus(container, seat); 668 seat_send_focus(container, seat);
658 669
659 container_set_dirty(container); 670 container_set_dirty(container);
660 container_set_dirty(container->parent); // for focused_inactive_child 671 container_set_dirty(container->parent); // for focused_inactive_child
661 if (last_focus) { 672 }
662 container_set_dirty(last_focus); 673
663 } 674 // emit ipc events
675 if (notify && new_workspace && last_workspace != new_workspace) {
676 ipc_event_workspace(last_workspace, new_workspace, "focus");
677 }
678 if (container->type == C_VIEW) {
679 ipc_event_window(container, "focus");
680 }
681
682 if (new_output_last_ws) {
683 workspace_consider_destroy(new_output_last_ws);
684 }
685
686 // Close any popups on the old focus
687 if (last_focus && last_focus->type == C_VIEW) {
688 view_close_popups(last_focus->sway_view);
664 } 689 }
665 690
666 // If urgent, either unset the urgency or start a timer to unset it 691 // If urgent, either unset the urgency or start a timer to unset it
667 if (container && container->type == C_VIEW && 692 if (container->type == C_VIEW && view_is_urgent(container->sway_view) &&
668 view_is_urgent(container->sway_view) &&
669 !container->sway_view->urgent_timer) { 693 !container->sway_view->urgent_timer) {
670 struct sway_view *view = container->sway_view; 694 struct sway_view *view = container->sway_view;
671 if (last_workspace && last_workspace != new_workspace && 695 if (last_workspace && last_workspace != new_workspace &&
@@ -687,46 +711,20 @@ void seat_set_focus_warp(struct sway_seat *seat,
687 711
688 // If we've focused a floating container, bring it to the front. 712 // If we've focused a floating container, bring it to the front.
689 // We do this by putting it at the end of the floating list. 713 // We do this by putting it at the end of the floating list.
690 if (container && container_is_floating(container)) { 714 struct sway_container *floater = container;
691 list_move_to_end( 715 while (floater->parent && floater->parent->type != C_WORKSPACE) {
692 container->parent->sway_workspace->floating, container); 716 floater = floater->parent;
693 } 717 }
694 718 if (container_is_floating(floater)) {
695 // clean up unfocused empty workspace on new output 719 list_move_to_end(floater->parent->sway_workspace->floating, floater);
696 if (new_output_last_ws) {
697 if (!workspace_is_visible(new_output_last_ws)
698 && workspace_is_empty(new_output_last_ws)) {
699 if (last_workspace == new_output_last_ws) {
700 last_focus = NULL;
701 last_workspace = NULL;
702 }
703 container_destroy(new_output_last_ws);
704 }
705 }
706
707 // Close any popups on the old focus
708 if (last_focus && last_focus != container) {
709 if (last_focus->type == C_VIEW) {
710 view_close_popups(last_focus->sway_view);
711 }
712 } 720 }
713 721
714 if (last_focus) { 722 if (last_focus) {
715 if (last_workspace) { 723 if (last_workspace) {
716 if (notify && last_workspace != new_workspace) { 724 workspace_consider_destroy(last_workspace);
717 ipc_event_workspace(last_workspace, new_workspace, "focus");
718 }
719 if (!workspace_is_visible(last_workspace)
720 && workspace_is_empty(last_workspace)) {
721 if (last_workspace == last_focus) {
722 last_focus = NULL;
723 }
724 container_destroy(last_workspace);
725 }
726 } 725 }
727 726
728 if (config->mouse_warping && warp) { 727 if (config->mouse_warping && warp && new_output != last_output) {
729 if (new_output && last_output && new_output != last_output) {
730 double x = container->x + container->width / 2.0; 728 double x = container->x + container->width / 2.0;
731 double y = container->y + container->height / 2.0; 729 double y = container->y + container->height / 2.0;
732 struct wlr_output *wlr_output = 730 struct wlr_output *wlr_output =
@@ -737,20 +735,11 @@ void seat_set_focus_warp(struct sway_seat *seat,
737 seat->cursor->cursor->y)) { 735 seat->cursor->cursor->y)) {
738 wlr_cursor_warp(seat->cursor->cursor, NULL, x, y); 736 wlr_cursor_warp(seat->cursor->cursor, NULL, x, y);
739 cursor_send_pointer_motion(seat->cursor, 0, true); 737 cursor_send_pointer_motion(seat->cursor, 0, true);
740 }
741 } 738 }
742 } 739 }
743 } 740 }
744 741
745 if (container) { 742 seat->has_focus = true;
746 if (container->type == C_VIEW) {
747 ipc_event_window(container, "focus");
748 } else if (container->type == C_WORKSPACE) {
749 ipc_event_workspace(NULL, container, "focus");
750 }
751 }
752
753 seat->has_focus = (container != NULL);
754 743
755 update_debug_tree(); 744 update_debug_tree();
756} 745}
diff --git a/sway/ipc-server.c b/sway/ipc-server.c
index 34e940ad..ed710be5 100644
--- a/sway/ipc-server.c
+++ b/sway/ipc-server.c
@@ -31,6 +31,7 @@
31#include "sway/server.h" 31#include "sway/server.h"
32#include "sway/input/input-manager.h" 32#include "sway/input/input-manager.h"
33#include "sway/input/seat.h" 33#include "sway/input/seat.h"
34#include "sway/tree/root.h"
34#include "sway/tree/view.h" 35#include "sway/tree/view.h"
35#include "list.h" 36#include "list.h"
36#include "log.h" 37#include "log.h"
@@ -615,7 +616,7 @@ void ipc_client_handle_command(struct ipc_client *client) {
615 } 616 }
616 } 617 }
617 struct sway_output *output; 618 struct sway_output *output;
618 wl_list_for_each(output, &root_container.sway_root->outputs, link) { 619 wl_list_for_each(output, &root_container.sway_root->all_outputs, link) {
619 if (!output->swayc) { 620 if (!output->swayc) {
620 json_object_array_add(outputs, 621 json_object_array_add(outputs,
621 ipc_json_describe_disabled_output(output)); 622 ipc_json_describe_disabled_output(output));
diff --git a/sway/main.c b/sway/main.c
index 3ba4ba75..7ed10c86 100644
--- a/sway/main.c
+++ b/sway/main.c
@@ -23,7 +23,7 @@
23#include "sway/desktop/transaction.h" 23#include "sway/desktop/transaction.h"
24#include "sway/server.h" 24#include "sway/server.h"
25#include "sway/swaynag.h" 25#include "sway/swaynag.h"
26#include "sway/tree/layout.h" 26#include "sway/tree/root.h"
27#include "sway/ipc-server.h" 27#include "sway/ipc-server.h"
28#include "ipc-client.h" 28#include "ipc-client.h"
29#include "readline.h" 29#include "readline.h"
diff --git a/sway/meson.build b/sway/meson.build
index 676422d0..bcb44e8b 100644
--- a/sway/meson.build
+++ b/sway/meson.build
@@ -150,7 +150,6 @@ sway_sources = files(
150 150
151 'tree/arrange.c', 151 'tree/arrange.c',
152 'tree/container.c', 152 'tree/container.c',
153 'tree/layout.c',
154 'tree/root.c', 153 'tree/root.c',
155 'tree/view.c', 154 'tree/view.c',
156 'tree/workspace.c', 155 'tree/workspace.c',
diff --git a/sway/server.c b/sway/server.c
index 00acaa01..7fa6007e 100644
--- a/sway/server.c
+++ b/sway/server.c
@@ -24,7 +24,7 @@
24#include "sway/desktop/idle_inhibit_v1.h" 24#include "sway/desktop/idle_inhibit_v1.h"
25#include "sway/input/input-manager.h" 25#include "sway/input/input-manager.h"
26#include "sway/server.h" 26#include "sway/server.h"
27#include "sway/tree/layout.h" 27#include "sway/tree/root.h"
28#include "config.h" 28#include "config.h"
29#ifdef HAVE_XWAYLAND 29#ifdef HAVE_XWAYLAND
30#include "sway/xwayland.h" 30#include "sway/xwayland.h"
diff --git a/sway/sway.5.scd b/sway/sway.5.scd
index 83188067..927bf55c 100644
--- a/sway/sway.5.scd
+++ b/sway/sway.5.scd
@@ -133,10 +133,15 @@ They are expected to be used with *bindsym* or at runtime through *swaymsg*(1).
133 tiled containers. 133 tiled containers.
134 134
135*move* [absolute] position <pos\_x> [px] <pos\_y> [px] 135*move* [absolute] position <pos\_x> [px] <pos\_y> [px]
136 Moves the focused container to the specified position. 136 Moves the focused container to the specified position in the workspace. If
137 _absolute_ is used, the position is relative to all outputs.
137 138
138*move* [absolute] position center|mouse 139*move* [absolute] position center
139 Moves the focused container to be centered on the workspace or mouse. 140 Moves the focused container to be centered on the workspace. If _absolute_
141 is used, it is moved to the center of all outputs.
142
143*move* position cursor|mouse|pointer
144 Moves the focused container to be centered on the cursor.
140 145
141*move* container|window [to] mark <mark> 146*move* container|window [to] mark <mark>
142 Moves the focused container to the specified mark. 147 Moves the focused container to the specified mark.
diff --git a/sway/tree/arrange.c b/sway/tree/arrange.c
index a4b058f3..92f20fcc 100644
--- a/sway/tree/arrange.c
+++ b/sway/tree/arrange.c
@@ -7,39 +7,29 @@
7#include <wlr/types/wlr_output_layout.h> 7#include <wlr/types/wlr_output_layout.h>
8#include "sway/tree/arrange.h" 8#include "sway/tree/arrange.h"
9#include "sway/tree/container.h" 9#include "sway/tree/container.h"
10#include "sway/tree/layout.h"
11#include "sway/output.h" 10#include "sway/output.h"
12#include "sway/tree/workspace.h" 11#include "sway/tree/workspace.h"
13#include "sway/tree/view.h" 12#include "sway/tree/view.h"
14#include "list.h" 13#include "list.h"
15#include "log.h" 14#include "log.h"
16 15
17static void apply_horiz_layout(struct sway_container *parent) { 16static void apply_horiz_layout(list_t *children, struct wlr_box *parent) {
18 size_t num_children = parent->children->length; 17 if (!children->length) {
19 if (!num_children) {
20 return; 18 return;
21 } 19 }
22 size_t parent_offset = 0;
23 if (parent->parent->layout == L_TABBED) {
24 parent_offset = container_titlebar_height();
25 } else if (parent->parent->layout == L_STACKED) {
26 parent_offset = container_titlebar_height() *
27 parent->parent->children->length;
28 }
29 size_t parent_height = parent->height - parent_offset;
30 20
31 // Calculate total width of children 21 // Calculate total width of children
32 double total_width = 0; 22 double total_width = 0;
33 for (size_t i = 0; i < num_children; ++i) { 23 for (int i = 0; i < children->length; ++i) {
34 struct sway_container *child = parent->children->items[i]; 24 struct sway_container *child = children->items[i];
35 if (child->width <= 0) { 25 if (child->width <= 0) {
36 if (num_children > 1) { 26 if (children->length > 1) {
37 child->width = parent->width / (num_children - 1); 27 child->width = parent->width / (children->length - 1);
38 } else { 28 } else {
39 child->width = parent->width; 29 child->width = parent->width;
40 } 30 }
41 } 31 }
42 remove_gaps(child); 32 container_remove_gaps(child);
43 total_width += child->width; 33 total_width += child->width;
44 } 34 }
45 double scale = parent->width / total_width; 35 double scale = parent->width / total_width;
@@ -47,63 +37,48 @@ static void apply_horiz_layout(struct sway_container *parent) {
47 // Resize windows 37 // Resize windows
48 wlr_log(WLR_DEBUG, "Arranging %p horizontally", parent); 38 wlr_log(WLR_DEBUG, "Arranging %p horizontally", parent);
49 double child_x = parent->x; 39 double child_x = parent->x;
50 for (size_t i = 0; i < num_children; ++i) { 40 for (int i = 0; i < children->length; ++i) {
51 struct sway_container *child = parent->children->items[i]; 41 struct sway_container *child = children->items[i];
52 wlr_log(WLR_DEBUG,
53 "Calculating arrangement for %p:%d (will scale %f by %f)",
54 child, child->type, child->width, scale);
55 child->x = child_x; 42 child->x = child_x;
56 child->y = parent->y + parent_offset; 43 child->y = parent->y;
57 child->width = floor(child->width * scale); 44 child->width = floor(child->width * scale);
58 child->height = parent_height; 45 child->height = parent->height;
59 child_x += child->width; 46 child_x += child->width;
60 47
61 // Make last child use remaining width of parent 48 // Make last child use remaining width of parent
62 if (i == num_children - 1) { 49 if (i == children->length - 1) {
63 child->width = parent->x + parent->width - child->x; 50 child->width = parent->x + parent->width - child->x;
64 } 51 }
65 add_gaps(child); 52 container_add_gaps(child);
66 } 53 }
67} 54}
68 55
69static void apply_vert_layout(struct sway_container *parent) { 56static void apply_vert_layout(list_t *children, struct wlr_box *parent) {
70 size_t num_children = parent->children->length; 57 if (!children->length) {
71 if (!num_children) {
72 return; 58 return;
73 } 59 }
74 size_t parent_offset = 0;
75 if (parent->parent->layout == L_TABBED) {
76 parent_offset = container_titlebar_height();
77 } else if (parent->parent->layout == L_STACKED) {
78 parent_offset =
79 container_titlebar_height() * parent->parent->children->length;
80 }
81 size_t parent_height = parent->height + parent_offset;
82 60
83 // Calculate total height of children 61 // Calculate total height of children
84 double total_height = 0; 62 double total_height = 0;
85 for (size_t i = 0; i < num_children; ++i) { 63 for (int i = 0; i < children->length; ++i) {
86 struct sway_container *child = parent->children->items[i]; 64 struct sway_container *child = children->items[i];
87 if (child->height <= 0) { 65 if (child->height <= 0) {
88 if (num_children > 1) { 66 if (children->length > 1) {
89 child->height = parent_height / (num_children - 1); 67 child->height = parent->height / (children->length - 1);
90 } else { 68 } else {
91 child->height = parent_height; 69 child->height = parent->height;
92 } 70 }
93 } 71 }
94 remove_gaps(child); 72 container_remove_gaps(child);
95 total_height += child->height; 73 total_height += child->height;
96 } 74 }
97 double scale = parent_height / total_height; 75 double scale = parent->height / total_height;
98 76
99 // Resize 77 // Resize
100 wlr_log(WLR_DEBUG, "Arranging %p vertically", parent); 78 wlr_log(WLR_DEBUG, "Arranging %p vertically", parent);
101 double child_y = parent->y + parent_offset; 79 double child_y = parent->y;
102 for (size_t i = 0; i < num_children; ++i) { 80 for (int i = 0; i < children->length; ++i) {
103 struct sway_container *child = parent->children->items[i]; 81 struct sway_container *child = children->items[i];
104 wlr_log(WLR_DEBUG,
105 "Calculating arrangement for %p:%d (will scale %f by %f)",
106 child, child->type, child->height, scale);
107 child->x = parent->x; 82 child->x = parent->x;
108 child->y = child_y; 83 child->y = child_y;
109 child->width = parent->width; 84 child->width = parent->width;
@@ -111,101 +86,112 @@ static void apply_vert_layout(struct sway_container *parent) {
111 child_y += child->height; 86 child_y += child->height;
112 87
113 // Make last child use remaining height of parent 88 // Make last child use remaining height of parent
114 if (i == num_children - 1) { 89 if (i == children->length - 1) {
115 child->height = 90 child->height = parent->y + parent->height - child->y;
116 parent->y + parent_offset + parent_height - child->y;
117 } 91 }
118 add_gaps(child); 92 container_add_gaps(child);
119 } 93 }
120} 94}
121 95
122static void apply_tabbed_or_stacked_layout(struct sway_container *parent) { 96static void apply_tabbed_layout(list_t *children, struct wlr_box *parent) {
123 if (!parent->children->length) { 97 if (!children->length) {
124 return; 98 return;
125 } 99 }
126 size_t parent_offset = 0; 100 size_t parent_offset = container_titlebar_height();
127 if (parent->parent->layout == L_TABBED) {
128 parent_offset = container_titlebar_height();
129 } else if (parent->parent->layout == L_STACKED) {
130 parent_offset =
131 container_titlebar_height() * parent->parent->children->length;
132 }
133 size_t parent_height = parent->height - parent_offset; 101 size_t parent_height = parent->height - parent_offset;
134 for (int i = 0; i < parent->children->length; ++i) { 102 for (int i = 0; i < children->length; ++i) {
135 struct sway_container *child = parent->children->items[i]; 103 struct sway_container *child = children->items[i];
136 remove_gaps(child); 104 container_remove_gaps(child);
137 child->x = parent->x; 105 child->x = parent->x;
138 child->y = parent->y + parent_offset; 106 child->y = parent->y + parent_offset;
139 child->width = parent->width; 107 child->width = parent->width;
140 child->height = parent_height; 108 child->height = parent_height;
141 add_gaps(child); 109 container_add_gaps(child);
142 } 110 }
143} 111}
144 112
145static void arrange_children_of(struct sway_container *parent); 113static void apply_stacked_layout(list_t *children, struct wlr_box *parent) {
114 if (!children->length) {
115 return;
116 }
117 size_t parent_offset = container_titlebar_height() * children->length;
118 size_t parent_height = parent->height - parent_offset;
119 for (int i = 0; i < children->length; ++i) {
120 struct sway_container *child = children->items[i];
121 container_remove_gaps(child);
122 child->x = parent->x;
123 child->y = parent->y + parent_offset;
124 child->width = parent->width;
125 child->height = parent_height;
126 container_add_gaps(child);
127 }
128}
146 129
147static void arrange_floating(list_t *floating) { 130static void arrange_floating(list_t *floating) {
148 for (int i = 0; i < floating->length; ++i) { 131 for (int i = 0; i < floating->length; ++i) {
149 struct sway_container *floater = floating->items[i]; 132 struct sway_container *floater = floating->items[i];
150 if (floater->type == C_VIEW) { 133 arrange_container(floater);
151 view_autoconfigure(floater->sway_view);
152 } else {
153 arrange_children_of(floater);
154 }
155 container_set_dirty(floater);
156 } 134 }
157} 135}
158 136
159static void arrange_children_of(struct sway_container *parent) { 137static void arrange_children(list_t *children,
160 if (config->reloading) { 138 enum sway_container_layout layout, struct wlr_box *parent) {
161 return;
162 }
163 wlr_log(WLR_DEBUG, "Arranging layout for %p %s %fx%f+%f,%f", parent,
164 parent->name, parent->width, parent->height, parent->x, parent->y);
165
166 // Calculate x, y, width and height of children 139 // Calculate x, y, width and height of children
167 switch (parent->layout) { 140 switch (layout) {
168 case L_HORIZ: 141 case L_HORIZ:
169 apply_horiz_layout(parent); 142 apply_horiz_layout(children, parent);
170 break; 143 break;
171 case L_VERT: 144 case L_VERT:
172 apply_vert_layout(parent); 145 apply_vert_layout(children, parent);
173 break; 146 break;
174 case L_TABBED: 147 case L_TABBED:
148 apply_tabbed_layout(children, parent);
149 break;
175 case L_STACKED: 150 case L_STACKED:
176 apply_tabbed_or_stacked_layout(parent); 151 apply_stacked_layout(children, parent);
177 break; 152 break;
178 case L_NONE: 153 case L_NONE:
179 apply_horiz_layout(parent); 154 apply_horiz_layout(children, parent);
180 break; 155 break;
181 } 156 }
182 157
183 // Recurse into child containers 158 // Recurse into child containers
184 for (int i = 0; i < parent->children->length; ++i) { 159 for (int i = 0; i < children->length; ++i) {
185 struct sway_container *child = parent->children->items[i]; 160 struct sway_container *child = children->items[i];
186 if (parent->has_gaps && !child->has_gaps) { 161 arrange_container(child);
187 child->has_gaps = true;
188 child->gaps_inner = parent->gaps_inner;
189 child->gaps_outer = parent->gaps_outer;
190 }
191 if (child->type == C_VIEW) {
192 view_autoconfigure(child->sway_view);
193 } else {
194 arrange_children_of(child);
195 }
196 container_set_dirty(child);
197 } 162 }
198} 163}
199 164
200static void arrange_workspace(struct sway_container *workspace) { 165void arrange_container(struct sway_container *container) {
201 if (config->reloading) { 166 if (config->reloading) {
202 return; 167 return;
203 } 168 }
169 if (container->type == C_VIEW) {
170 view_autoconfigure(container->sway_view);
171 container_set_dirty(container);
172 return;
173 }
174 if (!sway_assert(container->type == C_CONTAINER, "Expected a container")) {
175 return;
176 }
177 struct wlr_box box;
178 container_get_box(container, &box);
179 arrange_children(container->children, container->layout, &box);
180 container_set_dirty(container);
181}
182
183void arrange_workspace(struct sway_container *workspace) {
184 if (config->reloading) {
185 return;
186 }
187 if (!sway_assert(workspace->type == C_WORKSPACE, "Expected a workspace")) {
188 return;
189 }
204 struct sway_container *output = workspace->parent; 190 struct sway_container *output = workspace->parent;
205 struct wlr_box *area = &output->sway_output->usable_area; 191 struct wlr_box *area = &output->sway_output->usable_area;
206 wlr_log(WLR_DEBUG, "Usable area for ws: %dx%d@%d,%d", 192 wlr_log(WLR_DEBUG, "Usable area for ws: %dx%d@%d,%d",
207 area->width, area->height, area->x, area->y); 193 area->width, area->height, area->x, area->y);
208 remove_gaps(workspace); 194 workspace_remove_gaps(workspace);
209 195
210 double prev_x = workspace->x; 196 double prev_x = workspace->x;
211 double prev_y = workspace->y; 197 double prev_y = workspace->y;
@@ -217,20 +203,22 @@ static void arrange_workspace(struct sway_container *workspace) {
217 // Adjust any floating containers 203 // Adjust any floating containers
218 double diff_x = workspace->x - prev_x; 204 double diff_x = workspace->x - prev_x;
219 double diff_y = workspace->y - prev_y; 205 double diff_y = workspace->y - prev_y;
220 for (int i = 0; i < workspace->sway_workspace->floating->length; ++i) { 206 if (diff_x != 0 || diff_y != 0) {
221 struct sway_container *floater = 207 for (int i = 0; i < workspace->sway_workspace->floating->length; ++i) {
222 workspace->sway_workspace->floating->items[i]; 208 struct sway_container *floater =
223 container_floating_translate(floater, diff_x, diff_y); 209 workspace->sway_workspace->floating->items[i];
224 double center_x = floater->x + floater->width / 2; 210 container_floating_translate(floater, diff_x, diff_y);
225 double center_y = floater->y + floater->height / 2; 211 double center_x = floater->x + floater->width / 2;
226 struct wlr_box workspace_box; 212 double center_y = floater->y + floater->height / 2;
227 container_get_box(workspace, &workspace_box); 213 struct wlr_box workspace_box;
228 if (!wlr_box_contains_point(&workspace_box, center_x, center_y)) { 214 container_get_box(workspace, &workspace_box);
229 container_floating_move_to_center(floater); 215 if (!wlr_box_contains_point(&workspace_box, center_x, center_y)) {
216 container_floating_move_to_center(floater);
217 }
230 } 218 }
231 } 219 }
232 220
233 add_gaps(workspace); 221 workspace_add_gaps(workspace);
234 container_set_dirty(workspace); 222 container_set_dirty(workspace);
235 wlr_log(WLR_DEBUG, "Arranging workspace '%s' at %f, %f", workspace->name, 223 wlr_log(WLR_DEBUG, "Arranging workspace '%s' at %f, %f", workspace->name,
236 workspace->x, workspace->y); 224 workspace->x, workspace->y);
@@ -240,22 +228,22 @@ static void arrange_workspace(struct sway_container *workspace) {
240 fs->y = workspace->parent->y; 228 fs->y = workspace->parent->y;
241 fs->width = workspace->parent->width; 229 fs->width = workspace->parent->width;
242 fs->height = workspace->parent->height; 230 fs->height = workspace->parent->height;
243 if (fs->type == C_VIEW) { 231 arrange_container(fs);
244 view_autoconfigure(fs->sway_view);
245 } else {
246 arrange_children_of(fs);
247 }
248 container_set_dirty(fs);
249 } else { 232 } else {
233 struct wlr_box box;
234 container_get_box(workspace, &box);
235 arrange_children(workspace->children, workspace->layout, &box);
250 arrange_floating(workspace->sway_workspace->floating); 236 arrange_floating(workspace->sway_workspace->floating);
251 arrange_children_of(workspace);
252 } 237 }
253} 238}
254 239
255static void arrange_output(struct sway_container *output) { 240void arrange_output(struct sway_container *output) {
256 if (config->reloading) { 241 if (config->reloading) {
257 return; 242 return;
258 } 243 }
244 if (!sway_assert(output->type == C_OUTPUT, "Expected an output")) {
245 return;
246 }
259 const struct wlr_box *output_box = wlr_output_layout_get_box( 247 const struct wlr_box *output_box = wlr_output_layout_get_box(
260 root_container.sway_root->output_layout, 248 root_container.sway_root->output_layout,
261 output->sway_output->wlr_output); 249 output->sway_output->wlr_output);
@@ -272,7 +260,7 @@ static void arrange_output(struct sway_container *output) {
272 } 260 }
273} 261}
274 262
275static void arrange_root() { 263void arrange_root(void) {
276 if (config->reloading) { 264 if (config->reloading) {
277 return; 265 return;
278 } 266 }
@@ -303,52 +291,10 @@ void arrange_windows(struct sway_container *container) {
303 arrange_workspace(container); 291 arrange_workspace(container);
304 break; 292 break;
305 case C_CONTAINER: 293 case C_CONTAINER:
306 arrange_children_of(container);
307 container_set_dirty(container);
308 break;
309 case C_VIEW: 294 case C_VIEW:
310 view_autoconfigure(container->sway_view); 295 arrange_container(container);
311 container_set_dirty(container);
312 break; 296 break;
313 case C_TYPES: 297 case C_TYPES:
314 break; 298 break;
315 } 299 }
316} 300}
317
318void remove_gaps(struct sway_container *c) {
319 if (c->current_gaps == 0) {
320 wlr_log(WLR_DEBUG, "Removing gaps: not gapped: %p", c);
321 return;
322 }
323
324 c->width += c->current_gaps * 2;
325 c->height += c->current_gaps * 2;
326 c->x -= c->current_gaps;
327 c->y -= c->current_gaps;
328
329 c->current_gaps = 0;
330
331 wlr_log(WLR_DEBUG, "Removing gaps %p", c);
332}
333
334void add_gaps(struct sway_container *c) {
335 if (c->current_gaps > 0 || c->type == C_CONTAINER) {
336 wlr_log(WLR_DEBUG, "Not adding gaps: %p", c);
337 return;
338 }
339
340 if (c->type == C_WORKSPACE &&
341 !(config->edge_gaps || (config->smart_gaps && c->children->length > 1))) {
342 return;
343 }
344
345 double gaps = c->has_gaps ? c->gaps_inner : config->gaps_inner;
346
347 c->x += gaps;
348 c->y += gaps;
349 c->width -= 2 * gaps;
350 c->height -= 2 * gaps;
351 c->current_gaps = gaps;
352
353 wlr_log(WLR_DEBUG, "Adding gaps: %p", c);
354}
diff --git a/sway/tree/container.c b/sway/tree/container.c
index a8c6e667..ee019098 100644
--- a/sway/tree/container.c
+++ b/sway/tree/container.c
@@ -19,7 +19,6 @@
19#include "sway/output.h" 19#include "sway/output.h"
20#include "sway/server.h" 20#include "sway/server.h"
21#include "sway/tree/arrange.h" 21#include "sway/tree/arrange.h"
22#include "sway/tree/layout.h"
23#include "sway/tree/view.h" 22#include "sway/tree/view.h"
24#include "sway/tree/workspace.h" 23#include "sway/tree/workspace.h"
25#include "log.h" 24#include "log.h"
@@ -43,14 +42,12 @@ const char *container_type_to_str(enum sway_container_type type) {
43} 42}
44 43
45void container_create_notify(struct sway_container *container) { 44void container_create_notify(struct sway_container *container) {
46 // TODO send ipc event type based on the container type
47 wl_signal_emit(&root_container.sway_root->events.new_container, container);
48
49 if (container->type == C_VIEW) { 45 if (container->type == C_VIEW) {
50 ipc_event_window(container, "new"); 46 ipc_event_window(container, "new");
51 } else if (container->type == C_WORKSPACE) { 47 } else if (container->type == C_WORKSPACE) {
52 ipc_event_workspace(NULL, container, "init"); 48 ipc_event_workspace(NULL, container, "init");
53 } 49 }
50 wl_signal_emit(&root_container.sway_root->events.new_container, container);
54} 51}
55 52
56void container_update_textures_recursive(struct sway_container *con) { 53void container_update_textures_recursive(struct sway_container *con) {
@@ -104,210 +101,53 @@ struct sway_container *container_create(enum sway_container_type type) {
104 return c; 101 return c;
105} 102}
106 103
107static void container_workspace_free(struct sway_workspace *ws) { 104void container_destroy(struct sway_container *con) {
108 list_foreach(ws->output_priority, free); 105 if (!sway_assert(con->type == C_CONTAINER || con->type == C_VIEW,
109 list_free(ws->output_priority); 106 "Expected a container or view")) {
110 list_free(ws->floating); 107 return;
111 free(ws); 108 }
112} 109 if (!sway_assert(con->destroying,
113
114void container_free(struct sway_container *cont) {
115 if (!sway_assert(cont->destroying,
116 "Tried to free container which wasn't marked as destroying")) { 110 "Tried to free container which wasn't marked as destroying")) {
117 return; 111 return;
118 } 112 }
119 if (!sway_assert(cont->ntxnrefs == 0, "Tried to free container " 113 if (!sway_assert(con->ntxnrefs == 0, "Tried to free container "
120 "which is still referenced by transactions")) { 114 "which is still referenced by transactions")) {
121 return; 115 return;
122 } 116 }
123 free(cont->name); 117 free(con->name);
124 free(cont->formatted_title); 118 free(con->formatted_title);
125 wlr_texture_destroy(cont->title_focused); 119 wlr_texture_destroy(con->title_focused);
126 wlr_texture_destroy(cont->title_focused_inactive); 120 wlr_texture_destroy(con->title_focused_inactive);
127 wlr_texture_destroy(cont->title_unfocused); 121 wlr_texture_destroy(con->title_unfocused);
128 wlr_texture_destroy(cont->title_urgent); 122 wlr_texture_destroy(con->title_urgent);
129 list_free(cont->children); 123 list_free(con->children);
130 list_free(cont->current.children); 124 list_free(con->current.children);
131 list_free(cont->outputs); 125 list_free(con->outputs);
132
133 switch (cont->type) {
134 case C_ROOT:
135 break;
136 case C_OUTPUT:
137 break;
138 case C_WORKSPACE:
139 container_workspace_free(cont->sway_workspace);
140 break;
141 case C_CONTAINER:
142 break;
143 case C_VIEW:
144 {
145 struct sway_view *view = cont->sway_view;
146 view->swayc = NULL;
147 free(view->title_format);
148 view->title_format = NULL;
149
150 if (view->destroying) {
151 view_free(view);
152 }
153 }
154 break;
155 case C_TYPES:
156 sway_assert(false, "Didn't expect to see C_TYPES here");
157 break;
158 }
159
160 free(cont);
161}
162
163static struct sway_container *container_destroy_noreaping(
164 struct sway_container *con);
165
166static struct sway_container *container_workspace_destroy(
167 struct sway_container *workspace) {
168 if (!sway_assert(workspace, "cannot destroy null workspace")) {
169 return NULL;
170 }
171
172 struct sway_container *output = container_parent(workspace, C_OUTPUT);
173
174 // If we're destroying the output, it will be NULL here. Return the root so
175 // that it doesn't appear that the workspace has refused to be destoyed,
176 // which would leave it in a broken state with no parent.
177 if (output == NULL) {
178 return &root_container;
179 }
180
181 // Do not destroy this if it's the last workspace on this output
182 if (output->children->length == 1) {
183 return NULL;
184 }
185
186 wlr_log(WLR_DEBUG, "destroying workspace '%s'", workspace->name);
187
188 if (!workspace_is_empty(workspace)) {
189 // Move children to a different workspace on this output
190 struct sway_container *new_workspace = NULL;
191 for (int i = 0; i < output->children->length; i++) {
192 if (output->children->items[i] != workspace) {
193 new_workspace = output->children->items[i];
194 break;
195 }
196 }
197
198 wlr_log(WLR_DEBUG, "moving children to different workspace '%s' -> '%s'",
199 workspace->name, new_workspace->name);
200 for (int i = 0; i < workspace->children->length; i++) {
201 container_move_to(workspace->children->items[i], new_workspace);
202 }
203 list_t *floating = workspace->sway_workspace->floating;
204 for (int i = 0; i < floating->length; i++) {
205 struct sway_container *floater = floating->items[i];
206 container_remove_child(floater);
207 workspace_add_floating(new_workspace, floater);
208 }
209 }
210
211 return output;
212}
213
214static void untrack_output(struct sway_container *con, void *data) {
215 struct sway_output *output = data;
216 int index = list_find(con->outputs, output);
217 if (index != -1) {
218 list_del(con->outputs, index);
219 }
220}
221
222static struct sway_container *container_output_destroy(
223 struct sway_container *output) {
224 if (!sway_assert(output, "cannot destroy null output")) {
225 return NULL;
226 }
227
228 if (output->children->length > 0) {
229 // TODO save workspaces when there are no outputs.
230 // TODO also check if there will ever be no outputs except for exiting
231 // program
232 if (root_container.children->length > 1) {
233 // Move workspace from this output to another output
234 struct sway_container *fallback_output =
235 root_container.children->items[0];
236 if (fallback_output == output) {
237 fallback_output = root_container.children->items[1];
238 }
239
240 while (output->children->length) {
241 struct sway_container *workspace = output->children->items[0];
242
243 struct sway_container *new_output =
244 workspace_output_get_highest_available(workspace, output);
245 if (!new_output) {
246 new_output = fallback_output;
247 workspace_output_add_priority(workspace, new_output);
248 }
249 126
250 container_remove_child(workspace); 127 if (con->type == C_VIEW) {
251 if (!workspace_is_empty(workspace)) { 128 struct sway_view *view = con->sway_view;
252 container_add_child(new_output, workspace); 129 view->swayc = NULL;
253 ipc_event_workspace(NULL, workspace, "move"); 130 free(view->title_format);
254 } else { 131 view->title_format = NULL;
255 container_destroy(workspace);
256 }
257 132
258 output_sort_workspaces(new_output); 133 if (view->destroying) {
259 } 134 view_destroy(view);
260 } 135 }
261 } 136 }
262 137
263 root_for_each_container(untrack_output, output->sway_output); 138 free(con);
264
265 wl_list_remove(&output->sway_output->mode.link);
266 wl_list_remove(&output->sway_output->transform.link);
267 wl_list_remove(&output->sway_output->scale.link);
268
269 wl_list_remove(&output->sway_output->damage_destroy.link);
270 wl_list_remove(&output->sway_output->damage_frame.link);
271
272 output->sway_output->swayc = NULL;
273 output->sway_output = NULL;
274
275 wlr_log(WLR_DEBUG, "OUTPUT: Destroying output '%s'", output->name);
276
277 return &root_container;
278} 139}
279 140
280/** 141void container_begin_destroy(struct sway_container *con) {
281 * Implement the actual destroy logic, without reaping. 142 if (!sway_assert(con->type == C_CONTAINER || con->type == C_VIEW,
282 */ 143 "Expected a container or view")) {
283static struct sway_container *container_destroy_noreaping( 144 return;
284 struct sway_container *con) {
285 if (con == NULL) {
286 return NULL;
287 }
288 if (con->destroying) {
289 return NULL;
290 } 145 }
291 146
292 wl_signal_emit(&con->events.destroy, con);
293
294 // emit IPC event
295 if (con->type == C_VIEW) { 147 if (con->type == C_VIEW) {
296 ipc_event_window(con, "close"); 148 ipc_event_window(con, "close");
297 } else if (con->type == C_WORKSPACE) {
298 ipc_event_workspace(NULL, con, "empty");
299 }
300
301 // The below functions move their children to somewhere else.
302 if (con->type == C_OUTPUT) {
303 container_output_destroy(con);
304 } else if (con->type == C_WORKSPACE) {
305 // Workspaces will refuse to be destroyed if they're the last workspace
306 // on their output.
307 if (!container_workspace_destroy(con)) {
308 return NULL;
309 }
310 } 149 }
150 wl_signal_emit(&con->events.destroy, con);
311 151
312 container_end_mouse_operation(con); 152 container_end_mouse_operation(con);
313 153
@@ -318,51 +158,22 @@ static struct sway_container *container_destroy_noreaping(
318 root_scratchpad_remove_container(con); 158 root_scratchpad_remove_container(con);
319 } 159 }
320 160
321 if (!con->parent) { 161 if (con->parent) {
322 return NULL; 162 container_remove_child(con);
323 }
324
325 return container_remove_child(con);
326}
327
328bool container_reap_empty(struct sway_container *con) {
329 switch (con->type) {
330 case C_ROOT:
331 case C_OUTPUT:
332 // dont reap these
333 break;
334 case C_WORKSPACE:
335 if (!workspace_is_visible(con) && workspace_is_empty(con)) {
336 wlr_log(WLR_DEBUG, "Destroying workspace via reaper");
337 container_destroy_noreaping(con);
338 return true;
339 }
340 break;
341 case C_CONTAINER:
342 if (con->children->length == 0) {
343 container_destroy_noreaping(con);
344 return true;
345 }
346 case C_VIEW:
347 break;
348 case C_TYPES:
349 sway_assert(false, "container_reap_empty called on an invalid "
350 "container");
351 break;
352 } 163 }
353
354 return false;
355} 164}
356 165
357struct sway_container *container_reap_empty_recursive( 166struct sway_container *container_reap_empty(struct sway_container *con) {
358 struct sway_container *con) { 167 while (con && con->type == C_CONTAINER) {
359 while (con) {
360 struct sway_container *next = con->parent; 168 struct sway_container *next = con->parent;
361 if (!container_reap_empty(con)) { 169 if (con->children->length == 0) {
362 break; 170 container_begin_destroy(con);
363 } 171 }
364 con = next; 172 con = next;
365 } 173 }
174 if (con && con->type == C_WORKSPACE) {
175 workspace_consider_destroy(con);
176 }
366 return con; 177 return con;
367} 178}
368 179
@@ -371,34 +182,12 @@ struct sway_container *container_flatten(struct sway_container *container) {
371 struct sway_container *child = container->children->items[0]; 182 struct sway_container *child = container->children->items[0];
372 struct sway_container *parent = container->parent; 183 struct sway_container *parent = container->parent;
373 container_replace_child(container, child); 184 container_replace_child(container, child);
374 container_destroy_noreaping(container); 185 container_begin_destroy(container);
375 container = parent; 186 container = parent;
376 } 187 }
377 return container; 188 return container;
378} 189}
379 190
380/**
381 * container_destroy() is the first step in destroying a container. We'll emit
382 * events, detach it from the tree and mark it as destroying. The container will
383 * remain in memory until it's no longer used by a transaction, then it will be
384 * freed via container_free().
385 *
386 * This function just wraps container_destroy_noreaping(), then does reaping.
387 */
388struct sway_container *container_destroy(struct sway_container *con) {
389 if (con->is_fullscreen) {
390 struct sway_container *ws = container_parent(con, C_WORKSPACE);
391 ws->sway_workspace->fullscreen = NULL;
392 }
393 struct sway_container *parent = container_destroy_noreaping(con);
394
395 if (!parent) {
396 return NULL;
397 }
398
399 return container_reap_empty_recursive(parent);
400}
401
402static void container_close_func(struct sway_container *container, void *data) { 191static void container_close_func(struct sway_container *container, void *data) {
403 if (container->type == C_VIEW) { 192 if (container->type == C_VIEW) {
404 view_close(container->sway_view); 193 view_close(container->sway_view);
@@ -687,10 +476,11 @@ struct sway_container *container_at(struct sway_container *workspace,
687 return NULL; 476 return NULL;
688 } 477 }
689 struct sway_container *c; 478 struct sway_container *c;
690 // Focused view's popups
691 struct sway_seat *seat = input_manager_current_seat(input_manager); 479 struct sway_seat *seat = input_manager_current_seat(input_manager);
692 struct sway_container *focus = 480 struct sway_container *focus =
693 seat_get_focus_inactive(seat, &root_container); 481 seat_get_focus_inactive(seat, &root_container);
482 bool is_floating = focus && container_is_floating_or_child(focus);
483 // Focused view's popups
694 if (focus && focus->type == C_VIEW) { 484 if (focus && focus->type == C_VIEW) {
695 surface_at_view(focus, lx, ly, surface, sx, sy); 485 surface_at_view(focus, lx, ly, surface, sx, sy);
696 if (*surface && surface_is_popup(*surface)) { 486 if (*surface && surface_is_popup(*surface)) {
@@ -698,11 +488,27 @@ struct sway_container *container_at(struct sway_container *workspace,
698 } 488 }
699 *surface = NULL; 489 *surface = NULL;
700 } 490 }
701 // Floating 491 // If focused is floating, focused view's non-popups
492 if (focus && focus->type == C_VIEW && is_floating) {
493 surface_at_view(focus, lx, ly, surface, sx, sy);
494 if (*surface) {
495 return focus;
496 }
497 *surface = NULL;
498 }
499 // Floating (non-focused)
702 if ((c = floating_container_at(lx, ly, surface, sx, sy))) { 500 if ((c = floating_container_at(lx, ly, surface, sx, sy))) {
703 return c; 501 return c;
704 } 502 }
705 // Tiling 503 // If focused is tiling, focused view's non-popups
504 if (focus && focus->type == C_VIEW && !is_floating) {
505 surface_at_view(focus, lx, ly, surface, sx, sy);
506 if (*surface) {
507 return focus;
508 }
509 *surface = NULL;
510 }
511 // Tiling (non-focused)
706 if ((c = tiling_container_at(workspace, lx, ly, surface, sx, sy))) { 512 if ((c = tiling_container_at(workspace, lx, ly, surface, sx, sy))) {
707 return c; 513 return c;
708 } 514 }
@@ -1331,3 +1137,267 @@ void container_discover_outputs(struct sway_container *con) {
1331 } 1137 }
1332 } 1138 }
1333} 1139}
1140
1141void container_remove_gaps(struct sway_container *c) {
1142 if (!sway_assert(c->type == C_CONTAINER || c->type == C_VIEW,
1143 "Expected a container or view")) {
1144 return;
1145 }
1146 if (c->current_gaps == 0) {
1147 return;
1148 }
1149
1150 c->width += c->current_gaps * 2;
1151 c->height += c->current_gaps * 2;
1152 c->x -= c->current_gaps;
1153 c->y -= c->current_gaps;
1154 c->current_gaps = 0;
1155}
1156
1157void container_add_gaps(struct sway_container *c) {
1158 if (!sway_assert(c->type == C_CONTAINER || c->type == C_VIEW,
1159 "Expected a container or view")) {
1160 return;
1161 }
1162 if (c->current_gaps > 0) {
1163 return;
1164 }
1165 // Linear containers don't have gaps because it'd create double gaps
1166 if (c->type == C_CONTAINER &&
1167 c->layout != L_TABBED && c->layout != L_STACKED) {
1168 return;
1169 }
1170 // Children of tabbed/stacked containers re-use the gaps of the container
1171 enum sway_container_layout layout = c->parent->layout;
1172 if (layout == L_TABBED || layout == L_STACKED) {
1173 return;
1174 }
1175
1176 struct sway_container *ws = container_parent(c, C_WORKSPACE);
1177
1178 c->current_gaps = ws->has_gaps ? ws->gaps_inner : config->gaps_inner;
1179 c->x += c->current_gaps;
1180 c->y += c->current_gaps;
1181 c->width -= 2 * c->current_gaps;
1182 c->height -= 2 * c->current_gaps;
1183}
1184
1185int container_sibling_index(const struct sway_container *child) {
1186 return list_find(child->parent->children, child);
1187}
1188
1189void container_handle_fullscreen_reparent(struct sway_container *con,
1190 struct sway_container *old_parent) {
1191 if (!con->is_fullscreen) {
1192 return;
1193 }
1194 struct sway_container *old_workspace = old_parent;
1195 if (old_workspace && old_workspace->type != C_WORKSPACE) {
1196 old_workspace = container_parent(old_workspace, C_WORKSPACE);
1197 }
1198 struct sway_container *new_workspace = container_parent(con, C_WORKSPACE);
1199 if (old_workspace == new_workspace) {
1200 return;
1201 }
1202 // Unmark the old workspace as fullscreen
1203 if (old_workspace) {
1204 old_workspace->sway_workspace->fullscreen = NULL;
1205 }
1206
1207 // Mark the new workspace as fullscreen
1208 if (new_workspace->sway_workspace->fullscreen) {
1209 container_set_fullscreen(
1210 new_workspace->sway_workspace->fullscreen, false);
1211 }
1212 new_workspace->sway_workspace->fullscreen = con;
1213
1214 // Resize container to new output dimensions
1215 struct sway_container *output = new_workspace->parent;
1216 con->x = output->x;
1217 con->y = output->y;
1218 con->width = output->width;
1219 con->height = output->height;
1220
1221 if (con->type == C_VIEW) {
1222 struct sway_view *view = con->sway_view;
1223 view->x = output->x;
1224 view->y = output->y;
1225 view->width = output->width;
1226 view->height = output->height;
1227 } else {
1228 arrange_windows(new_workspace);
1229 }
1230}
1231
1232void container_insert_child(struct sway_container *parent,
1233 struct sway_container *child, int i) {
1234 struct sway_container *old_parent = child->parent;
1235 if (old_parent) {
1236 container_remove_child(child);
1237 }
1238 wlr_log(WLR_DEBUG, "Inserting id:%zd at index %d", child->id, i);
1239 list_insert(parent->children, i, child);
1240 child->parent = parent;
1241 container_handle_fullscreen_reparent(child, old_parent);
1242}
1243
1244struct sway_container *container_add_sibling(struct sway_container *fixed,
1245 struct sway_container *active) {
1246 // TODO handle floating
1247 struct sway_container *old_parent = NULL;
1248 if (active->parent) {
1249 old_parent = active->parent;
1250 container_remove_child(active);
1251 }
1252 struct sway_container *parent = fixed->parent;
1253 int i = container_sibling_index(fixed);
1254 list_insert(parent->children, i + 1, active);
1255 active->parent = parent;
1256 container_handle_fullscreen_reparent(active, old_parent);
1257 return active->parent;
1258}
1259
1260void container_add_child(struct sway_container *parent,
1261 struct sway_container *child) {
1262 wlr_log(WLR_DEBUG, "Adding %p (%d, %fx%f) to %p (%d, %fx%f)",
1263 child, child->type, child->width, child->height,
1264 parent, parent->type, parent->width, parent->height);
1265 struct sway_container *old_parent = child->parent;
1266 list_add(parent->children, child);
1267 child->parent = parent;
1268 container_handle_fullscreen_reparent(child, old_parent);
1269 if (old_parent) {
1270 container_set_dirty(old_parent);
1271 }
1272 container_set_dirty(child);
1273}
1274
1275struct sway_container *container_remove_child(struct sway_container *child) {
1276 if (child->is_fullscreen) {
1277 struct sway_container *workspace = container_parent(child, C_WORKSPACE);
1278 workspace->sway_workspace->fullscreen = NULL;
1279 }
1280
1281 struct sway_container *parent = child->parent;
1282 list_t *list = container_is_floating(child) ?
1283 parent->sway_workspace->floating : parent->children;
1284 int index = list_find(list, child);
1285 if (index != -1) {
1286 list_del(list, index);
1287 }
1288 child->parent = NULL;
1289 container_notify_subtree_changed(parent);
1290
1291 container_set_dirty(parent);
1292 container_set_dirty(child);
1293
1294 return parent;
1295}
1296
1297enum sway_container_layout container_get_default_layout(
1298 struct sway_container *con) {
1299 if (con->type != C_OUTPUT) {
1300 con = container_parent(con, C_OUTPUT);
1301 }
1302
1303 if (!sway_assert(con != NULL,
1304 "container_get_default_layout must be called on an attached"
1305 " container below the root container")) {
1306 return 0;
1307 }
1308
1309 if (config->default_layout != L_NONE) {
1310 return config->default_layout;
1311 } else if (config->default_orientation != L_NONE) {
1312 return config->default_orientation;
1313 } else if (con->width >= con->height) {
1314 return L_HORIZ;
1315 } else {
1316 return L_VERT;
1317 }
1318}
1319
1320struct sway_container *container_replace_child(struct sway_container *child,
1321 struct sway_container *new_child) {
1322 struct sway_container *parent = child->parent;
1323 if (parent == NULL) {
1324 return NULL;
1325 }
1326
1327 list_t *list = container_is_floating(child) ?
1328 parent->sway_workspace->floating : parent->children;
1329 int i = list_find(list, child);
1330
1331 if (new_child->parent) {
1332 container_remove_child(new_child);
1333 }
1334 list->items[i] = new_child;
1335 new_child->parent = parent;
1336 child->parent = NULL;
1337
1338 // Set geometry for new child
1339 new_child->x = child->x;
1340 new_child->y = child->y;
1341 new_child->width = child->width;
1342 new_child->height = child->height;
1343
1344 // reset geometry for child
1345 child->width = 0;
1346 child->height = 0;
1347
1348 return parent;
1349}
1350
1351struct sway_container *container_split(struct sway_container *child,
1352 enum sway_container_layout layout) {
1353 // TODO floating: cannot split a floating container
1354 if (!sway_assert(child, "child cannot be null")) {
1355 return NULL;
1356 }
1357 if (child->type == C_WORKSPACE && child->children->length == 0) {
1358 // Special case: this just behaves like splitt
1359 child->prev_split_layout = child->layout;
1360 child->layout = layout;
1361 return child;
1362 }
1363
1364 struct sway_container *cont = container_create(C_CONTAINER);
1365
1366 wlr_log(WLR_DEBUG, "creating container %p around %p", cont, child);
1367
1368 cont->prev_split_layout = L_NONE;
1369 cont->width = child->width;
1370 cont->height = child->height;
1371 cont->x = child->x;
1372 cont->y = child->y;
1373 cont->current_gaps = child->current_gaps;
1374
1375 struct sway_seat *seat = input_manager_get_default_seat(input_manager);
1376 bool set_focus = (seat_get_focus(seat) == child);
1377
1378 if (child->type == C_WORKSPACE) {
1379 struct sway_container *workspace = child;
1380 while (workspace->children->length) {
1381 struct sway_container *ws_child = workspace->children->items[0];
1382 container_remove_child(ws_child);
1383 container_add_child(cont, ws_child);
1384 }
1385
1386 container_add_child(workspace, cont);
1387 enum sway_container_layout old_layout = workspace->layout;
1388 workspace->layout = layout;
1389 cont->layout = old_layout;
1390 } else {
1391 cont->layout = layout;
1392 container_replace_child(child, cont);
1393 container_add_child(cont, child);
1394 }
1395
1396 if (set_focus) {
1397 seat_set_focus(seat, cont);
1398 seat_set_focus(seat, child);
1399 }
1400
1401 container_notify_subtree_changed(cont);
1402 return cont;
1403}
diff --git a/sway/tree/layout.c b/sway/tree/layout.c
deleted file mode 100644
index a3de44ce..00000000
--- a/sway/tree/layout.c
+++ /dev/null
@@ -1,1019 +0,0 @@
1#define _POSIX_C_SOURCE 200809L
2#include <math.h>
3#include <stdbool.h>
4#include <stdlib.h>
5#include <string.h>
6#include <wlr/types/wlr_output.h>
7#include <wlr/types/wlr_output_layout.h>
8#include "config.h"
9#include "sway/debug.h"
10#include "sway/tree/arrange.h"
11#include "sway/tree/container.h"
12#include "sway/tree/layout.h"
13#include "sway/output.h"
14#include "sway/tree/workspace.h"
15#include "sway/tree/view.h"
16#include "sway/input/seat.h"
17#include "sway/ipc-server.h"
18#include "list.h"
19#include "log.h"
20
21static int index_child(const struct sway_container *child) {
22 return list_find(child->parent->children, child);
23}
24
25static void container_handle_fullscreen_reparent(struct sway_container *con,
26 struct sway_container *old_parent) {
27 if (!con->is_fullscreen) {
28 return;
29 }
30 struct sway_container *old_workspace = old_parent;
31 if (old_workspace && old_workspace->type != C_WORKSPACE) {
32 old_workspace = container_parent(old_workspace, C_WORKSPACE);
33 }
34 struct sway_container *new_workspace = container_parent(con, C_WORKSPACE);
35 if (old_workspace == new_workspace) {
36 return;
37 }
38 // Unmark the old workspace as fullscreen
39 if (old_workspace) {
40 old_workspace->sway_workspace->fullscreen = NULL;
41 }
42
43 // Mark the new workspace as fullscreen
44 if (new_workspace->sway_workspace->fullscreen) {
45 container_set_fullscreen(
46 new_workspace->sway_workspace->fullscreen, false);
47 }
48 new_workspace->sway_workspace->fullscreen = con;
49
50 // Resize container to new output dimensions
51 struct sway_container *output = new_workspace->parent;
52 con->x = output->x;
53 con->y = output->y;
54 con->width = output->width;
55 con->height = output->height;
56
57 if (con->type == C_VIEW) {
58 struct sway_view *view = con->sway_view;
59 view->x = output->x;
60 view->y = output->y;
61 view->width = output->width;
62 view->height = output->height;
63 } else {
64 arrange_windows(new_workspace);
65 }
66}
67
68void container_insert_child(struct sway_container *parent,
69 struct sway_container *child, int i) {
70 struct sway_container *old_parent = child->parent;
71 if (old_parent) {
72 container_remove_child(child);
73 }
74 wlr_log(WLR_DEBUG, "Inserting id:%zd at index %d", child->id, i);
75 list_insert(parent->children, i, child);
76 child->parent = parent;
77 container_handle_fullscreen_reparent(child, old_parent);
78}
79
80struct sway_container *container_add_sibling(struct sway_container *fixed,
81 struct sway_container *active) {
82 // TODO handle floating
83 struct sway_container *old_parent = NULL;
84 if (active->parent) {
85 old_parent = active->parent;
86 container_remove_child(active);
87 }
88 struct sway_container *parent = fixed->parent;
89 int i = index_child(fixed);
90 list_insert(parent->children, i + 1, active);
91 active->parent = parent;
92 container_handle_fullscreen_reparent(active, old_parent);
93 return active->parent;
94}
95
96void container_add_child(struct sway_container *parent,
97 struct sway_container *child) {
98 wlr_log(WLR_DEBUG, "Adding %p (%d, %fx%f) to %p (%d, %fx%f)",
99 child, child->type, child->width, child->height,
100 parent, parent->type, parent->width, parent->height);
101 struct sway_container *old_parent = child->parent;
102 list_add(parent->children, child);
103 child->parent = parent;
104 container_handle_fullscreen_reparent(child, old_parent);
105 if (old_parent) {
106 container_set_dirty(old_parent);
107 }
108 container_set_dirty(child);
109}
110
111struct sway_container *container_remove_child(struct sway_container *child) {
112 if (child->is_fullscreen) {
113 struct sway_container *workspace = container_parent(child, C_WORKSPACE);
114 workspace->sway_workspace->fullscreen = NULL;
115 }
116
117 struct sway_container *parent = child->parent;
118 list_t *list = container_is_floating(child) ?
119 parent->sway_workspace->floating : parent->children;
120 int index = list_find(list, child);
121 if (index != -1) {
122 list_del(list, index);
123 }
124 child->parent = NULL;
125 container_notify_subtree_changed(parent);
126
127 container_set_dirty(parent);
128 container_set_dirty(child);
129
130 return parent;
131}
132
133void container_move_to(struct sway_container *container,
134 struct sway_container *destination) {
135 if (!sway_assert(container->type == C_CONTAINER ||
136 container->type == C_VIEW, "Expected a container or view")) {
137 return;
138 }
139 if (container == destination
140 || container_has_ancestor(container, destination)) {
141 return;
142 }
143 struct sway_container *old_parent = NULL;
144 struct sway_container *new_parent = NULL;
145 if (container_is_floating(container)) {
146 // Resolve destination into a workspace
147 struct sway_container *new_ws = NULL;
148 if (destination->type == C_OUTPUT) {
149 new_ws = output_get_active_workspace(destination->sway_output);
150 } else if (destination->type == C_WORKSPACE) {
151 new_ws = destination;
152 } else {
153 new_ws = container_parent(destination, C_WORKSPACE);
154 }
155 if (!new_ws) {
156 // This can happen if the user has run "move container to mark foo",
157 // where mark foo is on a hidden scratchpad container.
158 return;
159 }
160 struct sway_container *old_output =
161 container_parent(container, C_OUTPUT);
162 old_parent = container_remove_child(container);
163 workspace_add_floating(new_ws, container);
164 container_handle_fullscreen_reparent(container, old_parent);
165 // If changing output, center it within the workspace
166 if (old_output != new_ws->parent && !container->is_fullscreen) {
167 container_floating_move_to_center(container);
168 }
169 } else {
170 old_parent = container_remove_child(container);
171 container->width = container->height = 0;
172 container->saved_width = container->saved_height = 0;
173
174 if (destination->type == C_VIEW) {
175 new_parent = container_add_sibling(destination, container);
176 } else {
177 new_parent = destination;
178 container_add_child(destination, container);
179 }
180 }
181
182 if (container->type == C_VIEW) {
183 ipc_event_window(container, "move");
184 }
185 container_notify_subtree_changed(old_parent);
186 container_notify_subtree_changed(new_parent);
187
188 // If view was moved to a fullscreen workspace, refocus the fullscreen view
189 struct sway_container *new_workspace = container;
190 if (new_workspace->type != C_WORKSPACE) {
191 new_workspace = container_parent(new_workspace, C_WORKSPACE);
192 }
193 if (new_workspace->sway_workspace->fullscreen) {
194 struct sway_seat *seat;
195 struct sway_container *focus, *focus_ws;
196 wl_list_for_each(seat, &input_manager->seats, link) {
197 focus = seat_get_focus(seat);
198 focus_ws = focus;
199 if (focus_ws->type != C_WORKSPACE) {
200 focus_ws = container_parent(focus_ws, C_WORKSPACE);
201 }
202 if (focus_ws == new_workspace) {
203 struct sway_container *new_focus = seat_get_focus_inactive(seat,
204 new_workspace->sway_workspace->fullscreen);
205 seat_set_focus(seat, new_focus);
206 }
207 }
208 }
209 // Update workspace urgent state
210 struct sway_container *old_workspace = old_parent;
211 if (old_workspace->type != C_WORKSPACE) {
212 old_workspace = container_parent(old_workspace, C_WORKSPACE);
213 }
214 if (new_workspace != old_workspace) {
215 workspace_detect_urgent(new_workspace);
216 if (old_workspace) {
217 workspace_detect_urgent(old_workspace);
218 }
219 }
220}
221
222static bool sway_dir_to_wlr(enum movement_direction dir,
223 enum wlr_direction *out) {
224 switch (dir) {
225 case MOVE_UP:
226 *out = WLR_DIRECTION_UP;
227 break;
228 case MOVE_DOWN:
229 *out = WLR_DIRECTION_DOWN;
230 break;
231 case MOVE_LEFT:
232 *out = WLR_DIRECTION_LEFT;
233 break;
234 case MOVE_RIGHT:
235 *out = WLR_DIRECTION_RIGHT;
236 break;
237 default:
238 return false;
239 }
240
241 return true;
242}
243
244static bool is_parallel(enum sway_container_layout layout,
245 enum movement_direction dir) {
246 switch (layout) {
247 case L_TABBED:
248 case L_HORIZ:
249 return dir == MOVE_LEFT || dir == MOVE_RIGHT;
250 case L_STACKED:
251 case L_VERT:
252 return dir == MOVE_UP || dir == MOVE_DOWN;
253 default:
254 return false;
255 }
256}
257
258static enum movement_direction invert_movement(enum movement_direction dir) {
259 switch (dir) {
260 case MOVE_LEFT:
261 return MOVE_RIGHT;
262 case MOVE_RIGHT:
263 return MOVE_LEFT;
264 case MOVE_UP:
265 return MOVE_DOWN;
266 case MOVE_DOWN:
267 return MOVE_UP;
268 default:
269 sway_assert(0, "This function expects left|right|up|down");
270 return MOVE_LEFT;
271 }
272}
273
274static int move_offs(enum movement_direction move_dir) {
275 return move_dir == MOVE_LEFT || move_dir == MOVE_UP ? -1 : 1;
276}
277
278/* Gets the index of the most extreme member based on the movement offset */
279static int container_limit(struct sway_container *container,
280 enum movement_direction move_dir) {
281 return move_offs(move_dir) < 0 ? 0 : container->children->length;
282}
283
284/* Takes one child, sets it aside, wraps the rest of the children in a new
285 * container, switches the layout of the workspace, and drops the child back in.
286 * In other words, rejigger it. */
287static void workspace_rejigger(struct sway_container *ws,
288 struct sway_container *child, enum movement_direction move_dir) {
289 struct sway_container *original_parent = child->parent;
290 struct sway_container *new_parent =
291 container_split(ws, ws->layout);
292
293 container_remove_child(child);
294 for (int i = 0; i < ws->children->length; ++i) {
295 struct sway_container *_child = ws->children->items[i];
296 container_move_to(new_parent, _child);
297 }
298
299 int index = move_offs(move_dir);
300 container_insert_child(ws, child, index < 0 ? 0 : 1);
301 ws->layout =
302 move_dir == MOVE_LEFT || move_dir == MOVE_RIGHT ? L_HORIZ : L_VERT;
303
304 container_flatten(ws);
305 container_reap_empty_recursive(original_parent);
306 container_create_notify(new_parent);
307}
308
309static void move_out_of_tabs_stacks(struct sway_container *container,
310 struct sway_container *current, enum movement_direction move_dir,
311 int offs) {
312 if (container->parent == current->parent
313 && current->parent->children->length == 1) {
314 wlr_log(WLR_DEBUG, "Changing layout of %zd", current->parent->id);
315 current->parent->layout = move_dir ==
316 MOVE_LEFT || move_dir == MOVE_RIGHT ? L_HORIZ : L_VERT;
317 return;
318 }
319
320 wlr_log(WLR_DEBUG, "Moving out of tab/stack into a split");
321 bool is_workspace = current->parent->type == C_WORKSPACE;
322 struct sway_container *new_parent = container_split(current->parent,
323 move_dir == MOVE_LEFT || move_dir == MOVE_RIGHT ? L_HORIZ : L_VERT);
324 if (is_workspace) {
325 container_insert_child(new_parent->parent, container, offs < 0 ? 0 : 1);
326 } else {
327 container_insert_child(new_parent, container, offs < 0 ? 0 : 1);
328 container_reap_empty_recursive(new_parent->parent);
329 container_flatten(new_parent->parent);
330 }
331 container_create_notify(new_parent);
332 container_notify_subtree_changed(new_parent);
333}
334
335void container_move(struct sway_container *container,
336 enum movement_direction move_dir, int move_amt) {
337 if (!sway_assert(
338 container->type != C_CONTAINER || container->type != C_VIEW,
339 "Can only move containers and views")) {
340 return;
341 }
342 int offs = move_offs(move_dir);
343
344 struct sway_container *sibling = NULL;
345 struct sway_container *current = container;
346 struct sway_container *parent = current->parent;
347 struct sway_container *top = &root_container;
348
349 // If moving a fullscreen view, only consider outputs
350 if (container->is_fullscreen) {
351 current = container_parent(container, C_OUTPUT);
352 } else if (container_is_fullscreen_or_child(container) ||
353 container_is_floating_or_child(container)) {
354 // If we've fullscreened a split container, only allow the child to move
355 // around within the fullscreen parent.
356 // Same with floating a split container.
357 struct sway_container *ws = container_parent(container, C_WORKSPACE);
358 top = ws->sway_workspace->fullscreen;
359 }
360
361 struct sway_container *new_parent = container_flatten(parent);
362 if (new_parent != parent) {
363 // Special case: we were the last one in this container, so leave
364 return;
365 }
366
367 while (!sibling) {
368 if (current == top) {
369 return;
370 }
371
372 parent = current->parent;
373 wlr_log(WLR_DEBUG, "Visiting %p %s '%s'", current,
374 container_type_to_str(current->type), current->name);
375
376 int index = index_child(current);
377
378 switch (current->type) {
379 case C_OUTPUT: {
380 enum wlr_direction wlr_dir = 0;
381 if (!sway_assert(sway_dir_to_wlr(move_dir, &wlr_dir),
382 "got invalid direction: %d", move_dir)) {
383 return;
384 }
385 double ref_lx = current->x + current->width / 2;
386 double ref_ly = current->y + current->height / 2;
387 struct wlr_output *next = wlr_output_layout_adjacent_output(
388 root_container.sway_root->output_layout, wlr_dir,
389 current->sway_output->wlr_output, ref_lx, ref_ly);
390 if (!next) {
391 wlr_log(WLR_DEBUG, "Hit edge of output, nowhere else to go");
392 return;
393 }
394 struct sway_output *next_output = next->data;
395 current = next_output->swayc;
396 wlr_log(WLR_DEBUG, "Selected next output (%s)", current->name);
397 // Select workspace and get outta here
398 current = seat_get_focus_inactive(
399 config->handler_context.seat, current);
400 if (current->type != C_WORKSPACE) {
401 current = container_parent(current, C_WORKSPACE);
402 }
403 sibling = current;
404 break;
405 }
406 case C_WORKSPACE:
407 if (!is_parallel(current->layout, move_dir)) {
408 if (current->children->length >= 2) {
409 wlr_log(WLR_DEBUG, "Rejiggering the workspace (%d kiddos)",
410 current->children->length);
411 workspace_rejigger(current, container, move_dir);
412 return;
413 } else {
414 wlr_log(WLR_DEBUG, "Selecting output");
415 current = current->parent;
416 }
417 } else if (current->layout == L_TABBED
418 || current->layout == L_STACKED) {
419 wlr_log(WLR_DEBUG, "Rejiggering out of tabs/stacks");
420 workspace_rejigger(current, container, move_dir);
421 } else {
422 wlr_log(WLR_DEBUG, "Selecting output");
423 current = current->parent;
424 }
425 break;
426 case C_CONTAINER:
427 case C_VIEW:
428 if (is_parallel(parent->layout, move_dir)) {
429 if ((index == parent->children->length - 1 && offs > 0)
430 || (index == 0 && offs < 0)) {
431 if (current->parent == container->parent) {
432 if (!parent->is_fullscreen &&
433 (parent->layout == L_TABBED ||
434 parent->layout == L_STACKED)) {
435 move_out_of_tabs_stacks(container, current,
436 move_dir, offs);
437 return;
438 } else {
439 wlr_log(WLR_DEBUG, "Hit limit, selecting parent");
440 current = current->parent;
441 }
442 } else {
443 wlr_log(WLR_DEBUG, "Hit limit, "
444 "promoting descendant to sibling");
445 // Special case
446 container_insert_child(current->parent, container,
447 index + (offs < 0 ? 0 : 1));
448 container->width = container->height = 0;
449 return;
450 }
451 } else {
452 sibling = parent->children->items[index + offs];
453 wlr_log(WLR_DEBUG, "Selecting sibling id:%zd", sibling->id);
454 }
455 } else if (!parent->is_fullscreen && (parent->layout == L_TABBED ||
456 parent->layout == L_STACKED)) {
457 move_out_of_tabs_stacks(container, current, move_dir, offs);
458 return;
459 } else {
460 wlr_log(WLR_DEBUG, "Moving up to find a parallel container");
461 current = current->parent;
462 }
463 break;
464 default:
465 sway_assert(0, "Not expecting to see container of type %s here",
466 container_type_to_str(current->type));
467 return;
468 }
469 }
470
471 // Part two: move stuff around
472 int index = index_child(container);
473 struct sway_container *old_parent = container->parent;
474
475 while (sibling) {
476 switch (sibling->type) {
477 case C_VIEW:
478 if (sibling->parent == container->parent) {
479 wlr_log(WLR_DEBUG, "Swapping siblings");
480 sibling->parent->children->items[index + offs] = container;
481 sibling->parent->children->items[index] = sibling;
482 } else {
483 wlr_log(WLR_DEBUG, "Promoting to sibling of cousin");
484 container_insert_child(sibling->parent, container,
485 index_child(sibling) + (offs > 0 ? 0 : 1));
486 container->width = container->height = 0;
487 }
488 sibling = NULL;
489 break;
490 case C_WORKSPACE: // Note: only in the case of moving between outputs
491 case C_CONTAINER:
492 if (is_parallel(sibling->layout, move_dir)) {
493 int limit = container_limit(sibling, invert_movement(move_dir));
494 wlr_log(WLR_DEBUG, "limit: %d", limit);
495 wlr_log(WLR_DEBUG,
496 "Reparenting container (parallel) to index %d "
497 "(move dir: %d)", limit, move_dir);
498 container_insert_child(sibling, container, limit);
499 container->width = container->height = 0;
500 sibling = NULL;
501 } else {
502 wlr_log(WLR_DEBUG, "Reparenting container (perpendicular)");
503 struct sway_container *focus_inactive = seat_get_focus_inactive(
504 config->handler_context.seat, sibling);
505 if (focus_inactive && focus_inactive != sibling) {
506 while (focus_inactive->parent != sibling) {
507 focus_inactive = focus_inactive->parent;
508 }
509 wlr_log(WLR_DEBUG, "Focus inactive: id:%zd",
510 focus_inactive->id);
511 sibling = focus_inactive;
512 continue;
513 } else if (sibling->children->length) {
514 wlr_log(WLR_DEBUG, "No focus-inactive, adding arbitrarily");
515 container_remove_child(container);
516 container_add_sibling(sibling->children->items[0], container);
517 } else {
518 wlr_log(WLR_DEBUG, "No kiddos, adding container alone");
519 container_remove_child(container);
520 container_add_child(sibling, container);
521 }
522 container->width = container->height = 0;
523 sibling = NULL;
524 }
525 break;
526 default:
527 sway_assert(0, "Not expecting to see container of type %s here",
528 container_type_to_str(sibling->type));
529 return;
530 }
531 }
532
533 container_notify_subtree_changed(old_parent);
534 container_notify_subtree_changed(container->parent);
535
536 if (container->type == C_VIEW) {
537 ipc_event_window(container, "move");
538 }
539
540 if (old_parent) {
541 seat_set_focus(config->handler_context.seat, old_parent);
542 seat_set_focus(config->handler_context.seat, container);
543 }
544
545 struct sway_container *last_ws = old_parent;
546 struct sway_container *next_ws = container->parent;
547 if (last_ws && last_ws->type != C_WORKSPACE) {
548 last_ws = container_parent(last_ws, C_WORKSPACE);
549 }
550 if (next_ws && next_ws->type != C_WORKSPACE) {
551 next_ws = container_parent(next_ws, C_WORKSPACE);
552 }
553 if (last_ws && next_ws && last_ws != next_ws) {
554 ipc_event_workspace(last_ws, next_ws, "focus");
555 workspace_detect_urgent(last_ws);
556 workspace_detect_urgent(next_ws);
557 }
558 container_end_mouse_operation(container);
559}
560
561enum sway_container_layout container_get_default_layout(
562 struct sway_container *con) {
563 if (con->type != C_OUTPUT) {
564 con = container_parent(con, C_OUTPUT);
565 }
566
567 if (!sway_assert(con != NULL,
568 "container_get_default_layout must be called on an attached"
569 " container below the root container")) {
570 return 0;
571 }
572
573 if (config->default_layout != L_NONE) {
574 return config->default_layout;
575 } else if (config->default_orientation != L_NONE) {
576 return config->default_orientation;
577 } else if (con->width >= con->height) {
578 return L_HORIZ;
579 } else {
580 return L_VERT;
581 }
582}
583
584/**
585 * Get swayc in the direction of newly entered output.
586 */
587static struct sway_container *get_swayc_in_output_direction(
588 struct sway_container *output, enum movement_direction dir,
589 struct sway_seat *seat) {
590 if (!output) {
591 return NULL;
592 }
593
594 struct sway_container *ws = seat_get_focus_inactive(seat, output);
595 if (ws->type != C_WORKSPACE) {
596 ws = container_parent(ws, C_WORKSPACE);
597 }
598
599 if (ws == NULL) {
600 wlr_log(WLR_ERROR, "got an output without a workspace");
601 return NULL;
602 }
603
604 if (ws->children->length > 0) {
605 switch (dir) {
606 case MOVE_LEFT:
607 if (ws->layout == L_HORIZ || ws->layout == L_TABBED) {
608 // get most right child of new output
609 return ws->children->items[ws->children->length-1];
610 } else {
611 return seat_get_focus_inactive(seat, ws);
612 }
613 case MOVE_RIGHT:
614 if (ws->layout == L_HORIZ || ws->layout == L_TABBED) {
615 // get most left child of new output
616 return ws->children->items[0];
617 } else {
618 return seat_get_focus_inactive(seat, ws);
619 }
620 case MOVE_UP:
621 case MOVE_DOWN: {
622 struct sway_container *focused =
623 seat_get_focus_inactive(seat, ws);
624 if (focused && focused->parent) {
625 struct sway_container *parent = focused->parent;
626 if (parent->layout == L_VERT) {
627 if (dir == MOVE_UP) {
628 // get child furthest down on new output
629 int idx = parent->children->length - 1;
630 return parent->children->items[idx];
631 } else if (dir == MOVE_DOWN) {
632 // get child furthest up on new output
633 return parent->children->items[0];
634 }
635 }
636 return focused;
637 }
638 break;
639 }
640 default:
641 break;
642 }
643 }
644
645 return ws;
646}
647
648static struct sway_container *sway_output_from_wlr(struct wlr_output *output) {
649 if (output == NULL) {
650 return NULL;
651 }
652 for (int i = 0; i < root_container.children->length; ++i) {
653 struct sway_container *o = root_container.children->items[i];
654 if (o->type == C_OUTPUT && o->sway_output->wlr_output == output) {
655 return o;
656 }
657 }
658 return NULL;
659}
660
661struct sway_container *container_get_in_direction(
662 struct sway_container *container, struct sway_seat *seat,
663 enum movement_direction dir) {
664 struct sway_container *parent = container->parent;
665
666 if (dir == MOVE_CHILD) {
667 return seat_get_focus_inactive(seat, container);
668 }
669 if (container->is_fullscreen) {
670 if (dir == MOVE_PARENT) {
671 return NULL;
672 }
673 container = container_parent(container, C_OUTPUT);
674 parent = container->parent;
675 } else {
676 if (dir == MOVE_PARENT) {
677 if (parent->type == C_OUTPUT || container_is_floating(container)) {
678 return NULL;
679 } else {
680 return parent;
681 }
682 }
683 }
684
685 struct sway_container *wrap_candidate = NULL;
686 while (true) {
687 bool can_move = false;
688 int desired;
689 int idx = index_child(container);
690 if (idx == -1) {
691 return NULL;
692 }
693 if (parent->type == C_ROOT) {
694 enum wlr_direction wlr_dir = 0;
695 if (!sway_assert(sway_dir_to_wlr(dir, &wlr_dir),
696 "got invalid direction: %d", dir)) {
697 return NULL;
698 }
699 int lx = container->x + container->width / 2;
700 int ly = container->y + container->height / 2;
701 struct wlr_output_layout *layout =
702 root_container.sway_root->output_layout;
703 struct wlr_output *wlr_adjacent =
704 wlr_output_layout_adjacent_output(layout, wlr_dir,
705 container->sway_output->wlr_output, lx, ly);
706 struct sway_container *adjacent =
707 sway_output_from_wlr(wlr_adjacent);
708
709 if (!adjacent || adjacent == container) {
710 if (!wrap_candidate) {
711 return NULL;
712 }
713 return seat_get_focus_inactive_view(seat, wrap_candidate);
714 }
715 struct sway_container *next =
716 get_swayc_in_output_direction(adjacent, dir, seat);
717 if (next == NULL) {
718 return NULL;
719 }
720 struct sway_container *next_workspace = next;
721 if (next_workspace->type != C_WORKSPACE) {
722 next_workspace = container_parent(next_workspace, C_WORKSPACE);
723 }
724 sway_assert(next_workspace, "Next container has no workspace");
725 if (next_workspace->sway_workspace->fullscreen) {
726 return seat_get_focus_inactive(seat,
727 next_workspace->sway_workspace->fullscreen);
728 }
729 if (next->children && next->children->length) {
730 // TODO consider floating children as well
731 return seat_get_focus_inactive_view(seat, next);
732 } else {
733 return next;
734 }
735 } else {
736 if (dir == MOVE_LEFT || dir == MOVE_RIGHT) {
737 if (parent->layout == L_HORIZ || parent->layout == L_TABBED) {
738 can_move = true;
739 desired = idx + (dir == MOVE_LEFT ? -1 : 1);
740 }
741 } else {
742 if (parent->layout == L_VERT || parent->layout == L_STACKED) {
743 can_move = true;
744 desired = idx + (dir == MOVE_UP ? -1 : 1);
745 }
746 }
747 }
748
749 if (can_move) {
750 // TODO handle floating
751 if (desired < 0 || desired >= parent->children->length) {
752 can_move = false;
753 int len = parent->children->length;
754 if (config->focus_wrapping != WRAP_NO && !wrap_candidate
755 && len > 1) {
756 if (desired < 0) {
757 wrap_candidate = parent->children->items[len-1];
758 } else {
759 wrap_candidate = parent->children->items[0];
760 }
761 if (config->focus_wrapping == WRAP_FORCE) {
762 return seat_get_focus_inactive_view(seat,
763 wrap_candidate);
764 }
765 }
766 } else {
767 struct sway_container *desired_con =
768 parent->children->items[desired];
769 wlr_log(WLR_DEBUG,
770 "cont %d-%p dir %i sibling %d: %p", idx,
771 container, dir, desired, desired_con);
772 return seat_get_focus_inactive_view(seat, desired_con);
773 }
774 }
775
776 if (!can_move) {
777 container = parent;
778 parent = parent->parent;
779 if (!parent) {
780 // wrapping is the last chance
781 if (!wrap_candidate) {
782 return NULL;
783 }
784 return seat_get_focus_inactive_view(seat, wrap_candidate);
785 }
786 }
787 }
788}
789
790struct sway_container *container_replace_child(struct sway_container *child,
791 struct sway_container *new_child) {
792 struct sway_container *parent = child->parent;
793 if (parent == NULL) {
794 return NULL;
795 }
796
797 list_t *list = container_is_floating(child) ?
798 parent->sway_workspace->floating : parent->children;
799 int i = list_find(list, child);
800
801 if (new_child->parent) {
802 container_remove_child(new_child);
803 }
804 list->items[i] = new_child;
805 new_child->parent = parent;
806 child->parent = NULL;
807
808 // Set geometry for new child
809 new_child->x = child->x;
810 new_child->y = child->y;
811 new_child->width = child->width;
812 new_child->height = child->height;
813
814 // reset geometry for child
815 child->width = 0;
816 child->height = 0;
817
818 return parent;
819}
820
821struct sway_container *container_split(struct sway_container *child,
822 enum sway_container_layout layout) {
823 // TODO floating: cannot split a floating container
824 if (!sway_assert(child, "child cannot be null")) {
825 return NULL;
826 }
827 if (child->type == C_WORKSPACE && child->children->length == 0) {
828 // Special case: this just behaves like splitt
829 child->prev_split_layout = child->layout;
830 child->layout = layout;
831 return child;
832 }
833
834 struct sway_container *cont = container_create(C_CONTAINER);
835
836 wlr_log(WLR_DEBUG, "creating container %p around %p", cont, child);
837
838 remove_gaps(child);
839
840 cont->prev_split_layout = L_NONE;
841 cont->width = child->width;
842 cont->height = child->height;
843 cont->x = child->x;
844 cont->y = child->y;
845
846 struct sway_seat *seat = input_manager_get_default_seat(input_manager);
847 bool set_focus = (seat_get_focus(seat) == child);
848
849 add_gaps(cont);
850
851 if (child->type == C_WORKSPACE) {
852 struct sway_container *workspace = child;
853 while (workspace->children->length) {
854 struct sway_container *ws_child = workspace->children->items[0];
855 container_remove_child(ws_child);
856 container_add_child(cont, ws_child);
857 }
858
859 container_add_child(workspace, cont);
860 enum sway_container_layout old_layout = workspace->layout;
861 workspace->layout = layout;
862 cont->layout = old_layout;
863 } else {
864 cont->layout = layout;
865 container_replace_child(child, cont);
866 container_add_child(cont, child);
867 }
868
869 if (set_focus) {
870 seat_set_focus(seat, cont);
871 seat_set_focus(seat, child);
872 }
873
874 container_notify_subtree_changed(cont);
875 return cont;
876}
877
878void container_recursive_resize(struct sway_container *container,
879 double amount, enum wlr_edges edge) {
880 bool layout_match = true;
881 wlr_log(WLR_DEBUG, "Resizing %p with amount: %f", container, amount);
882 if (edge == WLR_EDGE_LEFT || edge == WLR_EDGE_RIGHT) {
883 container->width += amount;
884 layout_match = container->layout == L_HORIZ;
885 } else if (edge == WLR_EDGE_TOP || edge == WLR_EDGE_BOTTOM) {
886 container->height += amount;
887 layout_match = container->layout == L_VERT;
888 }
889 if (container->children) {
890 for (int i = 0; i < container->children->length; i++) {
891 struct sway_container *child = container->children->items[i];
892 double amt = layout_match ?
893 amount / container->children->length : amount;
894 container_recursive_resize(child, amt, edge);
895 }
896 }
897}
898
899static void swap_places(struct sway_container *con1,
900 struct sway_container *con2) {
901 struct sway_container *temp = malloc(sizeof(struct sway_container));
902 temp->x = con1->x;
903 temp->y = con1->y;
904 temp->width = con1->width;
905 temp->height = con1->height;
906 temp->parent = con1->parent;
907
908 con1->x = con2->x;
909 con1->y = con2->y;
910 con1->width = con2->width;
911 con1->height = con2->height;
912
913 con2->x = temp->x;
914 con2->y = temp->y;
915 con2->width = temp->width;
916 con2->height = temp->height;
917
918 int temp_index = index_child(con1);
919 container_insert_child(con2->parent, con1, index_child(con2));
920 container_insert_child(temp->parent, con2, temp_index);
921
922 free(temp);
923}
924
925static void swap_focus(struct sway_container *con1,
926 struct sway_container *con2, struct sway_seat *seat,
927 struct sway_container *focus) {
928 if (focus == con1 || focus == con2) {
929 struct sway_container *ws1 = container_parent(con1, C_WORKSPACE);
930 struct sway_container *ws2 = container_parent(con2, C_WORKSPACE);
931 if (focus == con1 && (con2->parent->layout == L_TABBED
932 || con2->parent->layout == L_STACKED)) {
933 if (workspace_is_visible(ws2)) {
934 seat_set_focus_warp(seat, con2, false, true);
935 }
936 seat_set_focus(seat, ws1 != ws2 ? con2 : con1);
937 } else if (focus == con2 && (con1->parent->layout == L_TABBED
938 || con1->parent->layout == L_STACKED)) {
939 if (workspace_is_visible(ws1)) {
940 seat_set_focus_warp(seat, con1, false, true);
941 }
942 seat_set_focus(seat, ws1 != ws2 ? con1 : con2);
943 } else if (ws1 != ws2) {
944 seat_set_focus(seat, focus == con1 ? con2 : con1);
945 } else {
946 seat_set_focus(seat, focus);
947 }
948 } else {
949 seat_set_focus(seat, focus);
950 }
951}
952
953void container_swap(struct sway_container *con1, struct sway_container *con2) {
954 if (!sway_assert(con1 && con2, "Cannot swap with nothing")) {
955 return;
956 }
957 if (!sway_assert(con1->type >= C_CONTAINER && con2->type >= C_CONTAINER,
958 "Can only swap containers and views")) {
959 return;
960 }
961 if (!sway_assert(!container_has_ancestor(con1, con2)
962 && !container_has_ancestor(con2, con1),
963 "Cannot swap ancestor and descendant")) {
964 return;
965 }
966 if (!sway_assert(!container_is_floating(con1)
967 && !container_is_floating(con2),
968 "Swapping with floating containers is not supported")) {
969 return;
970 }
971
972 wlr_log(WLR_DEBUG, "Swapping containers %zu and %zu", con1->id, con2->id);
973
974 int fs1 = con1->is_fullscreen;
975 int fs2 = con2->is_fullscreen;
976 if (fs1) {
977 container_set_fullscreen(con1, false);
978 }
979 if (fs2) {
980 container_set_fullscreen(con2, false);
981 }
982
983 struct sway_seat *seat = input_manager_get_default_seat(input_manager);
984 struct sway_container *focus = seat_get_focus(seat);
985 struct sway_container *vis1 = container_parent(
986 seat_get_focus_inactive(seat, container_parent(con1, C_OUTPUT)),
987 C_WORKSPACE);
988 struct sway_container *vis2 = container_parent(
989 seat_get_focus_inactive(seat, container_parent(con2, C_OUTPUT)),
990 C_WORKSPACE);
991
992 char *stored_prev_name = NULL;
993 if (prev_workspace_name) {
994 stored_prev_name = strdup(prev_workspace_name);
995 }
996
997 swap_places(con1, con2);
998
999 if (!workspace_is_visible(vis1)) {
1000 seat_set_focus(seat, seat_get_focus_inactive(seat, vis1));
1001 }
1002 if (!workspace_is_visible(vis2)) {
1003 seat_set_focus(seat, seat_get_focus_inactive(seat, vis2));
1004 }
1005
1006 swap_focus(con1, con2, seat, focus);
1007
1008 if (stored_prev_name) {
1009 free(prev_workspace_name);
1010 prev_workspace_name = stored_prev_name;
1011 }
1012
1013 if (fs1) {
1014 container_set_fullscreen(con2, true);
1015 }
1016 if (fs2) {
1017 container_set_fullscreen(con1, true);
1018 }
1019}
diff --git a/sway/tree/output.c b/sway/tree/output.c
index 6da63064..6601220b 100644
--- a/sway/tree/output.c
+++ b/sway/tree/output.c
@@ -10,6 +10,7 @@
10#include "log.h" 10#include "log.h"
11 11
12static void restore_workspaces(struct sway_container *output) { 12static void restore_workspaces(struct sway_container *output) {
13 // Workspace output priority
13 for (int i = 0; i < root_container.children->length; i++) { 14 for (int i = 0; i < root_container.children->length; i++) {
14 struct sway_container *other = root_container.children->items[i]; 15 struct sway_container *other = root_container.children->items[i];
15 if (other == output) { 16 if (other == output) {
@@ -29,6 +30,15 @@ static void restore_workspaces(struct sway_container *output) {
29 } 30 }
30 } 31 }
31 32
33 // Saved workspaces
34 list_t *saved = root_container.sway_root->saved_workspaces;
35 for (int i = 0; i < saved->length; ++i) {
36 struct sway_container *ws = saved->items[i];
37 container_add_child(output, ws);
38 ipc_event_workspace(NULL, ws, "move");
39 }
40 saved->length = 0;
41
32 output_sort_workspaces(output); 42 output_sort_workspaces(output);
33} 43}
34 44
@@ -68,7 +78,7 @@ struct sway_container *output_create(
68 output->sway_output = sway_output; 78 output->sway_output = sway_output;
69 output->name = strdup(name); 79 output->name = strdup(name);
70 if (output->name == NULL) { 80 if (output->name == NULL) {
71 container_destroy(output); 81 output_begin_destroy(output);
72 return NULL; 82 return NULL;
73 } 83 }
74 84
@@ -103,6 +113,120 @@ struct sway_container *output_create(
103 return output; 113 return output;
104} 114}
105 115
116static void output_evacuate(struct sway_container *output) {
117 if (!output->children->length) {
118 return;
119 }
120 struct sway_container *fallback_output = NULL;
121 if (root_container.children->length > 1) {
122 fallback_output = root_container.children->items[0];
123 if (fallback_output == output) {
124 fallback_output = root_container.children->items[1];
125 }
126 }
127
128 while (output->children->length) {
129 struct sway_container *workspace = output->children->items[0];
130
131 container_remove_child(workspace);
132
133 if (workspace_is_empty(workspace)) {
134 workspace_begin_destroy(workspace);
135 continue;
136 }
137
138 struct sway_container *new_output =
139 workspace_output_get_highest_available(workspace, output);
140 if (!new_output) {
141 new_output = fallback_output;
142 }
143
144 if (new_output) {
145 workspace_output_add_priority(workspace, new_output);
146 container_add_child(new_output, workspace);
147 output_sort_workspaces(new_output);
148 ipc_event_workspace(NULL, workspace, "move");
149 } else {
150 list_add(root_container.sway_root->saved_workspaces, workspace);
151 }
152 }
153}
154
155void output_destroy(struct sway_container *output) {
156 if (!sway_assert(output->type == C_OUTPUT, "Expected an output")) {
157 return;
158 }
159 if (!sway_assert(output->destroying,
160 "Tried to free output which wasn't marked as destroying")) {
161 return;
162 }
163 if (!sway_assert(output->ntxnrefs == 0, "Tried to free output "
164 "which is still referenced by transactions")) {
165 return;
166 }
167 free(output->name);
168 free(output->formatted_title);
169 wlr_texture_destroy(output->title_focused);
170 wlr_texture_destroy(output->title_focused_inactive);
171 wlr_texture_destroy(output->title_unfocused);
172 wlr_texture_destroy(output->title_urgent);
173 list_free(output->children);
174 list_free(output->current.children);
175 list_free(output->outputs);
176 free(output);
177
178 // NOTE: We don't actually destroy the sway_output here
179}
180
181static void untrack_output(struct sway_container *con, void *data) {
182 struct sway_output *output = data;
183 int index = list_find(con->outputs, output);
184 if (index != -1) {
185 list_del(con->outputs, index);
186 }
187}
188
189void output_begin_destroy(struct sway_container *output) {
190 if (!sway_assert(output->type == C_OUTPUT, "Expected an output")) {
191 return;
192 }
193 wlr_log(WLR_DEBUG, "OUTPUT: Destroying output '%s'", output->name);
194 wl_signal_emit(&output->events.destroy, output);
195
196 output_evacuate(output);
197
198 output->destroying = true;
199 container_set_dirty(output);
200
201 root_for_each_container(untrack_output, output->sway_output);
202
203 wl_list_remove(&output->sway_output->mode.link);
204 wl_list_remove(&output->sway_output->transform.link);
205 wl_list_remove(&output->sway_output->scale.link);
206 wl_list_remove(&output->sway_output->damage_destroy.link);
207 wl_list_remove(&output->sway_output->damage_frame.link);
208
209 output->sway_output->swayc = NULL;
210 output->sway_output = NULL;
211
212 if (output->parent) {
213 container_remove_child(output);
214 }
215}
216
217struct sway_container *output_from_wlr_output(struct wlr_output *output) {
218 if (output == NULL) {
219 return NULL;
220 }
221 for (int i = 0; i < root_container.children->length; ++i) {
222 struct sway_container *o = root_container.children->items[i];
223 if (o->type == C_OUTPUT && o->sway_output->wlr_output == output) {
224 return o;
225 }
226 }
227 return NULL;
228}
229
106void output_for_each_workspace(struct sway_container *output, 230void output_for_each_workspace(struct sway_container *output,
107 void (*f)(struct sway_container *con, void *data), void *data) { 231 void (*f)(struct sway_container *con, void *data), void *data) {
108 if (!sway_assert(output->type == C_OUTPUT, "Expected an output")) { 232 if (!sway_assert(output->type == C_OUTPUT, "Expected an output")) {
diff --git a/sway/tree/root.c b/sway/tree/root.c
index c27ff2c3..b42371de 100644
--- a/sway/tree/root.c
+++ b/sway/tree/root.c
@@ -3,6 +3,7 @@
3#include <stdlib.h> 3#include <stdlib.h>
4#include <string.h> 4#include <string.h>
5#include <wlr/types/wlr_output_layout.h> 5#include <wlr/types/wlr_output_layout.h>
6#include "sway/desktop/transaction.h"
6#include "sway/input/seat.h" 7#include "sway/input/seat.h"
7#include "sway/output.h" 8#include "sway/output.h"
8#include "sway/tree/arrange.h" 9#include "sway/tree/arrange.h"
@@ -32,13 +33,14 @@ void root_create(void) {
32 33
33 root_container.sway_root = calloc(1, sizeof(*root_container.sway_root)); 34 root_container.sway_root = calloc(1, sizeof(*root_container.sway_root));
34 root_container.sway_root->output_layout = wlr_output_layout_create(); 35 root_container.sway_root->output_layout = wlr_output_layout_create();
35 wl_list_init(&root_container.sway_root->outputs); 36 wl_list_init(&root_container.sway_root->all_outputs);
36#ifdef HAVE_XWAYLAND 37#ifdef HAVE_XWAYLAND
37 wl_list_init(&root_container.sway_root->xwayland_unmanaged); 38 wl_list_init(&root_container.sway_root->xwayland_unmanaged);
38#endif 39#endif
39 wl_list_init(&root_container.sway_root->drag_icons); 40 wl_list_init(&root_container.sway_root->drag_icons);
40 wl_signal_init(&root_container.sway_root->events.new_container); 41 wl_signal_init(&root_container.sway_root->events.new_container);
41 root_container.sway_root->scratchpad = create_list(); 42 root_container.sway_root->scratchpad = create_list();
43 root_container.sway_root->saved_workspaces = create_list();
42 44
43 root_container.sway_root->output_layout_change.notify = 45 root_container.sway_root->output_layout_change.notify =
44 output_layout_handle_change; 46 output_layout_handle_change;
@@ -50,6 +52,7 @@ void root_destroy(void) {
50 // sway_root 52 // sway_root
51 wl_list_remove(&root_container.sway_root->output_layout_change.link); 53 wl_list_remove(&root_container.sway_root->output_layout_change.link);
52 list_free(root_container.sway_root->scratchpad); 54 list_free(root_container.sway_root->scratchpad);
55 list_free(root_container.sway_root->saved_workspaces);
53 wlr_output_layout_destroy(root_container.sway_root->output_layout); 56 wlr_output_layout_destroy(root_container.sway_root->output_layout);
54 free(root_container.sway_root); 57 free(root_container.sway_root);
55 58
diff --git a/sway/tree/view.c b/sway/tree/view.c
index 7bf7325a..1a98c5f2 100644
--- a/sway/tree/view.c
+++ b/sway/tree/view.c
@@ -13,12 +13,12 @@
13#include "log.h" 13#include "log.h"
14#include "sway/criteria.h" 14#include "sway/criteria.h"
15#include "sway/commands.h" 15#include "sway/commands.h"
16#include "sway/desktop/transaction.h"
16#include "sway/ipc-server.h" 17#include "sway/ipc-server.h"
17#include "sway/output.h" 18#include "sway/output.h"
18#include "sway/input/seat.h" 19#include "sway/input/seat.h"
19#include "sway/tree/arrange.h" 20#include "sway/tree/arrange.h"
20#include "sway/tree/container.h" 21#include "sway/tree/container.h"
21#include "sway/tree/layout.h"
22#include "sway/tree/view.h" 22#include "sway/tree/view.h"
23#include "sway/tree/workspace.h" 23#include "sway/tree/workspace.h"
24#include "sway/config.h" 24#include "sway/config.h"
@@ -35,7 +35,7 @@ void view_init(struct sway_view *view, enum sway_view_type type,
35 wl_signal_init(&view->events.unmap); 35 wl_signal_init(&view->events.unmap);
36} 36}
37 37
38void view_free(struct sway_view *view) { 38void view_destroy(struct sway_view *view) {
39 if (!sway_assert(view->surface == NULL, "Tried to free mapped view")) { 39 if (!sway_assert(view->surface == NULL, "Tried to free mapped view")) {
40 return; 40 return;
41 } 41 }
@@ -75,14 +75,14 @@ void view_free(struct sway_view *view) {
75 * destroying flag will make the view get freed when the transaction is 75 * destroying flag will make the view get freed when the transaction is
76 * finished. 76 * finished.
77 */ 77 */
78void view_destroy(struct sway_view *view) { 78void view_begin_destroy(struct sway_view *view) {
79 if (!sway_assert(view->surface == NULL, "Tried to destroy a mapped view")) { 79 if (!sway_assert(view->surface == NULL, "Tried to destroy a mapped view")) {
80 return; 80 return;
81 } 81 }
82 view->destroying = true; 82 view->destroying = true;
83 83
84 if (!view->swayc) { 84 if (!view->swayc) {
85 view_free(view); 85 view_destroy(view);
86 } 86 }
87} 87}
88 88
@@ -225,15 +225,13 @@ void view_autoconfigure(struct sway_view *view) {
225 x = y = width = height = 0; 225 x = y = width = height = 0;
226 double y_offset = 0; 226 double y_offset = 0;
227 227
228 // In a tabbed or stacked container, the swayc's y is the top of the title 228 // In a tabbed or stacked container, the swayc's y is the bottom of the
229 // area. We have to offset the surface y by the height of the title bar, and 229 // title area. We have to disable any top border because the title bar is
230 // disable any top border because we'll always have the title bar. 230 // rendered by the parent.
231 if (con->parent->layout == L_TABBED) { 231 if (con->parent->layout == L_TABBED || con->parent->layout == L_STACKED) {
232 y_offset = container_titlebar_height();
233 view->border_top = false;
234 } else if (con->parent->layout == L_STACKED) {
235 y_offset = container_titlebar_height() * con->parent->children->length;
236 view->border_top = false; 232 view->border_top = false;
233 } else {
234 y_offset = container_titlebar_height();
237 } 235 }
238 236
239 enum sway_container_border border = view->border; 237 enum sway_container_border border = view->border;
@@ -244,17 +242,17 @@ void view_autoconfigure(struct sway_view *view) {
244 switch (border) { 242 switch (border) {
245 case B_NONE: 243 case B_NONE:
246 x = con->x; 244 x = con->x;
247 y = con->y + y_offset; 245 y = con->y;
248 width = con->width; 246 width = con->width;
249 height = con->height - y_offset; 247 height = con->height;
250 break; 248 break;
251 case B_PIXEL: 249 case B_PIXEL:
252 x = con->x + view->border_thickness * view->border_left; 250 x = con->x + view->border_thickness * view->border_left;
253 y = con->y + view->border_thickness * view->border_top + y_offset; 251 y = con->y + view->border_thickness * view->border_top;
254 width = con->width 252 width = con->width
255 - view->border_thickness * view->border_left 253 - view->border_thickness * view->border_left
256 - view->border_thickness * view->border_right; 254 - view->border_thickness * view->border_right;
257 height = con->height - y_offset 255 height = con->height
258 - view->border_thickness * view->border_top 256 - view->border_thickness * view->border_top
259 - view->border_thickness * view->border_bottom; 257 - view->border_thickness * view->border_bottom;
260 break; 258 break;
@@ -264,15 +262,9 @@ void view_autoconfigure(struct sway_view *view) {
264 width = con->width 262 width = con->width
265 - view->border_thickness * view->border_left 263 - view->border_thickness * view->border_left
266 - view->border_thickness * view->border_right; 264 - view->border_thickness * view->border_right;
267 if (y_offset) { 265 y = con->y + y_offset;
268 y = con->y + y_offset; 266 height = con->height - y_offset
269 height = con->height - y_offset 267 - view->border_thickness * view->border_bottom;
270 - view->border_thickness * view->border_bottom;
271 } else {
272 y = con->y + container_titlebar_height();
273 height = con->height - container_titlebar_height()
274 - view->border_thickness * view->border_bottom;
275 }
276 break; 268 break;
277 } 269 }
278 270
@@ -560,7 +552,9 @@ void view_unmap(struct sway_view *view) {
560 } 552 }
561 553
562 bool was_fullscreen = view->swayc->is_fullscreen; 554 bool was_fullscreen = view->swayc->is_fullscreen;
563 struct sway_container *surviving_ancestor = container_destroy(view->swayc); 555 struct sway_container *parent = view->swayc->parent;
556 container_begin_destroy(view->swayc);
557 struct sway_container *surviving_ancestor = container_reap_empty(parent);
564 558
565 // If the workspace wasn't reaped 559 // If the workspace wasn't reaped
566 if (surviving_ancestor && surviving_ancestor->type >= C_WORKSPACE) { 560 if (surviving_ancestor && surviving_ancestor->type >= C_WORKSPACE) {
diff --git a/sway/tree/workspace.c b/sway/tree/workspace.c
index cf50ee09..60256336 100644
--- a/sway/tree/workspace.c
+++ b/sway/tree/workspace.c
@@ -79,6 +79,65 @@ struct sway_container *workspace_create(struct sway_container *output,
79 return workspace; 79 return workspace;
80} 80}
81 81
82void workspace_destroy(struct sway_container *workspace) {
83 if (!sway_assert(workspace->type == C_WORKSPACE, "Expected a workspace")) {
84 return;
85 }
86 if (!sway_assert(workspace->destroying,
87 "Tried to free workspace which wasn't marked as destroying")) {
88 return;
89 }
90 if (!sway_assert(workspace->ntxnrefs == 0, "Tried to free workspace "
91 "which is still referenced by transactions")) {
92 return;
93 }
94 // sway_workspace
95 struct sway_workspace *ws = workspace->sway_workspace;
96 list_foreach(ws->output_priority, free);
97 list_free(ws->output_priority);
98 list_free(ws->floating);
99 free(ws);
100
101 // swayc
102 free(workspace->name);
103 free(workspace->formatted_title);
104 wlr_texture_destroy(workspace->title_focused);
105 wlr_texture_destroy(workspace->title_focused_inactive);
106 wlr_texture_destroy(workspace->title_unfocused);
107 wlr_texture_destroy(workspace->title_urgent);
108 list_free(workspace->children);
109 list_free(workspace->current.children);
110 list_free(workspace->outputs);
111 free(workspace);
112}
113
114void workspace_begin_destroy(struct sway_container *workspace) {
115 if (!sway_assert(workspace->type == C_WORKSPACE, "Expected a workspace")) {
116 return;
117 }
118 wlr_log(WLR_DEBUG, "Destroying workspace '%s'", workspace->name);
119 wl_signal_emit(&workspace->events.destroy, workspace);
120 ipc_event_workspace(NULL, workspace, "empty"); // intentional
121
122 workspace->destroying = true;
123 container_set_dirty(workspace);
124
125 if (workspace->parent) {
126 container_remove_child(workspace);
127 }
128}
129
130void workspace_consider_destroy(struct sway_container *ws) {
131 if (!sway_assert(ws->type == C_WORKSPACE, "Expected a workspace")) {
132 return;
133 }
134 struct sway_seat *seat = input_manager_current_seat(input_manager);
135 if (ws->children->length == 0 && ws->sway_workspace->floating->length == 0
136 && seat_get_active_child(seat, ws->parent) != ws) {
137 workspace_begin_destroy(ws);
138 }
139}
140
82char *prev_workspace_name = NULL; 141char *prev_workspace_name = NULL;
83 142
84void next_name_map(struct sway_container *ws, void *data) { 143void next_name_map(struct sway_container *ws, void *data) {
@@ -421,9 +480,7 @@ bool workspace_switch(struct sway_container *workspace,
421 // no op. We therefore need to send the IPC event and clean up the old 480 // no op. We therefore need to send the IPC event and clean up the old
422 // workspace here. 481 // workspace here.
423 ipc_event_workspace(active_ws, workspace, "focus"); 482 ipc_event_workspace(active_ws, workspace, "focus");
424 if (!workspace_is_visible(active_ws) && workspace_is_empty(active_ws)) { 483 workspace_consider_destroy(active_ws);
425 container_destroy(active_ws);
426 }
427 } 484 }
428 seat_set_focus(seat, next); 485 seat_set_focus(seat, next);
429 struct sway_container *output = container_parent(workspace, C_OUTPUT); 486 struct sway_container *output = container_parent(workspace, C_OUTPUT);
@@ -608,3 +665,46 @@ void workspace_add_floating(struct sway_container *workspace,
608 container_set_dirty(workspace); 665 container_set_dirty(workspace);
609 container_set_dirty(con); 666 container_set_dirty(con);
610} 667}
668
669void workspace_remove_gaps(struct sway_container *ws) {
670 if (!sway_assert(ws->type == C_WORKSPACE, "Expected a workspace")) {
671 return;
672 }
673 if (ws->current_gaps == 0) {
674 return;
675 }
676
677 ws->width += ws->current_gaps * 2;
678 ws->height += ws->current_gaps * 2;
679 ws->x -= ws->current_gaps;
680 ws->y -= ws->current_gaps;
681 ws->current_gaps = 0;
682}
683
684void workspace_add_gaps(struct sway_container *ws) {
685 if (!sway_assert(ws->type == C_WORKSPACE, "Expected a workspace")) {
686 return;
687 }
688 if (ws->current_gaps > 0) {
689 return;
690 }
691 bool should_apply =
692 config->edge_gaps || (config->smart_gaps && ws->children->length > 1);
693 if (!should_apply) {
694 return;
695 }
696
697 ws->current_gaps = ws->has_gaps ? ws->gaps_outer : config->gaps_outer;
698
699 if (ws->layout == L_TABBED || ws->layout == L_STACKED) {
700 // We have to add inner gaps for this, because children of tabbed and
701 // stacked containers don't apply their own gaps - they assume the
702 // tabbed/stacked container is using gaps.
703 ws->current_gaps += ws->has_gaps ? ws->gaps_inner : config->gaps_inner;
704 }
705
706 ws->x += ws->current_gaps;
707 ws->y += ws->current_gaps;
708 ws->width -= 2 * ws->current_gaps;
709 ws->height -= 2 * ws->current_gaps;
710}