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--completions/zsh/_swaylock6
-rw-r--r--include/sway/config.h2
-rw-r--r--include/sway/debug.h23
-rw-r--r--include/sway/output.h8
-rw-r--r--include/sway/server.h3
-rw-r--r--include/sway/tree/arrange.h6
-rw-r--r--include/sway/tree/container.h80
-rw-r--r--include/sway/tree/layout.h59
-rw-r--r--include/sway/tree/root.h4
-rw-r--r--include/sway/tree/view.h5
-rw-r--r--include/sway/tree/workspace.h13
-rw-r--r--include/swaynag/swaynag.h2
-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/move.c480
-rw-r--r--sway/commands/output.c1
-rw-r--r--sway/commands/resize.c30
-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.c10
-rw-r--r--sway/desktop/layer_shell.c1
-rw-r--r--sway/desktop/output.c14
-rw-r--r--sway/desktop/render.c28
-rw-r--r--sway/desktop/transaction.c87
-rw-r--r--sway/desktop/xdg_shell.c3
-rw-r--r--sway/desktop/xdg_shell_v6.c3
-rw-r--r--sway/desktop/xwayland.c3
-rw-r--r--sway/input/cursor.c1
-rw-r--r--sway/input/seat.c109
-rw-r--r--sway/ipc-json.c11
-rw-r--r--sway/ipc-server.c3
-rw-r--r--sway/main.c22
-rw-r--r--sway/meson.build1
-rw-r--r--sway/server.c9
-rw-r--r--sway/sway.5.scd11
-rw-r--r--sway/tree/arrange.c75
-rw-r--r--sway/tree/container.c648
-rw-r--r--sway/tree/layout.c1027
-rw-r--r--sway/tree/output.c126
-rw-r--r--sway/tree/root.c4
-rw-r--r--sway/tree/view.c72
-rw-r--r--sway/tree/workspace.c99
-rw-r--r--swaynag/config.c4
-rw-r--r--swaynag/main.c15
60 files changed, 1815 insertions, 1720 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/completions/zsh/_swaylock b/completions/zsh/_swaylock
index 478c7512..8fb4834c 100644
--- a/completions/zsh/_swaylock
+++ b/completions/zsh/_swaylock
@@ -6,9 +6,9 @@
6_arguments -s \ 6_arguments -s \
7 '(-v --version)'{-v,--version}'[Show the version number and quit]' \ 7 '(-v --version)'{-v,--version}'[Show the version number and quit]' \
8 '(-h --help)'{-h,--help}'[Show help message and quit]' \ 8 '(-h --help)'{-h,--help}'[Show help message and quit]' \
9 '(-f --daemonize)'{-f, --daemonize}'[Detach from the controlling terminal]'\ 9 '(-f --daemonize)'{-f,--daemonize}'[Detach from the controlling terminal]' \
10 '(-c --color)'{-c,--color}'[Specify a color (rrggbb)]' \ 10 '(-c --color)'{-c,--color}'[Specify a color (rrggbb)]' \
11 '(-i --image)'{-i,--image}'[Display an image]:files:_files' \ 11 '(-i --image)'{-i,--image}'[Display an image]:files:_files' \
12 '(-s --scaling)'{-s,--scaling}'[Scaling mode]:mode:(stretch fill fit center tile)' \ 12 '(-s --scaling)'{-s,--scaling}'[Scaling mode]:mode:(stretch fill fit center tile)' \
13 '(-u --no-unlock-indicator)'{-u,--no-unlock-indicator}'[Disable the unlock indicator]' 13 '(-u --no-unlock-indicator)'{-u,--no-unlock-indicator}'[Disable the unlock indicator]' \
14 '(--socket)'{--socket}'[Use the specified socket path.]:files:_files' \ 14 '(--socket)'--socket'[Use the specified socket path.]:files:_files' \
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/debug.h b/include/sway/debug.h
index 38d4eccd..bf3a5f6d 100644
--- a/include/sway/debug.h
+++ b/include/sway/debug.h
@@ -1,15 +1,22 @@
1#ifndef SWAY_DEBUG_H 1#ifndef SWAY_DEBUG_H
2#define SWAY_DEBUG_H 2#define SWAY_DEBUG_H
3#include <stdbool.h>
3 4
4// Tree 5struct sway_debug {
5extern bool enable_debug_tree; 6 bool noatomic; // Ignore atomic layout updates
6void update_debug_tree(); 7 bool render_tree; // Render the tree overlay
8 bool txn_timings; // Log verbose messages about transactions
9 bool txn_wait; // Always wait for the timeout before applying
10
11 enum {
12 DAMAGE_DEFAULT, // Default behaviour
13 DAMAGE_HIGHLIGHT, // Highlight regions of the screen being damaged
14 DAMAGE_RERENDER, // Render the full output when any damage occurs
15 } damage;
16};
7 17
8// Damage 18extern struct sway_debug debug;
9extern const char *damage_debug;
10 19
11// Transactions 20void update_debug_tree();
12extern int txn_timeout_ms;
13extern bool txn_debug;
14 21
15#endif 22#endif
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/server.h b/include/sway/server.h
index b93584b6..1e20f2c8 100644
--- a/include/sway/server.h
+++ b/include/sway/server.h
@@ -54,8 +54,7 @@ struct sway_server {
54 struct wl_listener server_decoration; 54 struct wl_listener server_decoration;
55 struct wl_list decorations; // sway_server_decoration::link 55 struct wl_list decorations; // sway_server_decoration::link
56 56
57 bool debug_txn_timings; 57 size_t txn_timeout_ms;
58
59 list_t *transactions; 58 list_t *transactions;
60 list_t *dirty_containers; 59 list_t *dirty_containers;
61}; 60};
diff --git a/include/sway/tree/arrange.h b/include/sway/tree/arrange.h
index d6abcc81..346103d3 100644
--- a/include/sway/tree/arrange.h
+++ b/include/sway/tree/arrange.h
@@ -4,12 +4,6 @@
4 4
5struct sway_container; 5struct sway_container;
6 6
7// Remove gaps around container
8void remove_gaps(struct sway_container *c);
9
10// Add gaps around container
11void add_gaps(struct sway_container *c);
12
13/** 7/**
14 * Arrange layout for all the children of the given container. 8 * Arrange layout for all the children of the given container.
15 */ 9 */
diff --git a/include/sway/tree/container.h b/include/sway/tree/container.h
index 5eccedc1..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;
@@ -138,6 +141,9 @@ struct sway_container {
138 141
139 struct sway_container *parent; 142 struct sway_container *parent;
140 143
144 // Outputs currently being intersected
145 list_t *outputs; // struct sway_output
146
141 // Indicates that the container is a scratchpad container. 147 // Indicates that the container is a scratchpad container.
142 // Both hidden and visible scratchpad containers have scratchpad=true. 148 // Both hidden and visible scratchpad containers have scratchpad=true.
143 // Hidden scratchpad containers have a NULL parent. 149 // Hidden scratchpad containers have a NULL parent.
@@ -166,39 +172,13 @@ struct sway_container {
166 172
167 struct { 173 struct {
168 struct wl_signal destroy; 174 struct wl_signal destroy;
169 // Raised after the tree updates, but before arrange_windows
170 // Passed the previous parent
171 struct wl_signal reparent;
172 } events; 175 } events;
173
174 struct wl_listener reparent;
175}; 176};
176 177
177struct sway_container *container_create(enum sway_container_type type); 178struct sway_container *container_create(enum sway_container_type type);
178 179
179const char *container_type_to_str(enum sway_container_type type); 180const char *container_type_to_str(enum sway_container_type type);
180 181
181struct sway_container *output_create(struct sway_output *sway_output);
182
183/**
184 * Create a new container container. A container container can be a a child of
185 * a workspace container or another container container.
186 */
187struct sway_container *container_container_create();
188
189/**
190 * Create a new output. Outputs are children of the root container and have no
191 * order in the tree structure.
192 */
193struct sway_container *output_create(struct sway_output *sway_output);
194
195/**
196 * Create a new workspace container. Workspaces are children of an output
197 * container and are ordered alphabetically by name.
198 */
199struct sway_container *workspace_create(struct sway_container *output,
200 const char *name);
201
202/* 182/*
203 * 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
204 * 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
@@ -207,9 +187,9 @@ struct sway_container *workspace_create(struct sway_container *output,
207struct sway_container *container_view_create( 187struct sway_container *container_view_create(
208 struct sway_container *sibling, struct sway_view *sway_view); 188 struct sway_container *sibling, struct sway_view *sway_view);
209 189
210void container_free(struct sway_container *cont); 190void container_destroy(struct sway_container *con);
211 191
212struct sway_container *container_destroy(struct sway_container *container); 192void container_begin_destroy(struct sway_container *con);
213 193
214struct sway_container *container_close(struct sway_container *container); 194struct sway_container *container_close(struct sway_container *container);
215 195
@@ -257,10 +237,7 @@ void container_update_textures_recursive(struct sway_container *con);
257 237
258void container_damage_whole(struct sway_container *container); 238void container_damage_whole(struct sway_container *container);
259 239
260bool container_reap_empty(struct sway_container *con); 240struct sway_container *container_reap_empty(struct sway_container *con);
261
262struct sway_container *container_reap_empty_recursive(
263 struct sway_container *con);
264 241
265struct sway_container *container_flatten(struct sway_container *container); 242struct sway_container *container_flatten(struct sway_container *container);
266 243
@@ -353,4 +330,43 @@ bool container_is_floating_or_child(struct sway_container *container);
353 */ 330 */
354bool container_is_fullscreen_or_child(struct sway_container *container); 331bool container_is_fullscreen_or_child(struct sway_container *container);
355 332
333/**
334 * Return the output which will be used for scale purposes.
335 * This is the most recently entered output.
336 */
337struct sway_output *container_get_effective_output(struct sway_container *con);
338
339void container_discover_outputs(struct sway_container *con);
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
356#endif 372#endif
diff --git a/include/sway/tree/layout.h b/include/sway/tree/layout.h
deleted file mode 100644
index 5b803dfe..00000000
--- a/include/sway/tree/layout.h
+++ /dev/null
@@ -1,59 +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 resize_edge {
19 RESIZE_EDGE_NONE = 0,
20 RESIZE_EDGE_LEFT = 1,
21 RESIZE_EDGE_RIGHT = 2,
22 RESIZE_EDGE_TOP = 4,
23 RESIZE_EDGE_BOTTOM = 8,
24};
25
26struct sway_container;
27
28void container_add_child(struct sway_container *parent,
29 struct sway_container *child);
30
31struct sway_container *container_add_sibling(struct sway_container *parent,
32 struct sway_container *child);
33
34struct sway_container *container_remove_child(struct sway_container *child);
35
36struct sway_container *container_replace_child(struct sway_container *child,
37 struct sway_container *new_child);
38
39void container_move_to(struct sway_container* container,
40 struct sway_container* destination);
41
42void container_move(struct sway_container *container,
43 enum movement_direction dir, int move_amt);
44
45enum sway_container_layout container_get_default_layout(
46 struct sway_container *con);
47
48struct sway_container *container_get_in_direction(struct sway_container
49 *container, struct sway_seat *seat, enum movement_direction dir);
50
51struct sway_container *container_split(struct sway_container *child,
52 enum sway_container_layout layout);
53
54void container_recursive_resize(struct sway_container *container,
55 double amount, enum resize_edge edge);
56
57void container_swap(struct sway_container *con1, struct sway_container *con2);
58
59#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 2747e7c4..f73ce571 100644
--- a/include/sway/tree/view.h
+++ b/include/sway/tree/view.h
@@ -120,7 +120,6 @@ struct sway_view {
120 } events; 120 } events;
121 121
122 struct wl_listener surface_new_subsurface; 122 struct wl_listener surface_new_subsurface;
123 struct wl_listener container_reparent;
124}; 123};
125 124
126struct sway_xdg_shell_v6_view { 125struct sway_xdg_shell_v6_view {
@@ -285,10 +284,10 @@ void view_for_each_popup(struct sway_view *view,
285void view_init(struct sway_view *view, enum sway_view_type type, 284void view_init(struct sway_view *view, enum sway_view_type type,
286 const struct sway_view_impl *impl); 285 const struct sway_view_impl *impl);
287 286
288void view_free(struct sway_view *view);
289
290void view_destroy(struct sway_view *view); 287void view_destroy(struct sway_view *view);
291 288
289void view_begin_destroy(struct sway_view *view);
290
292void view_map(struct sway_view *view, struct wlr_surface *wlr_surface); 291void view_map(struct sway_view *view, struct wlr_surface *wlr_surface);
293 292
294void 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/swaynag/swaynag.h b/include/swaynag/swaynag.h
index 1bf8b640..a32d1503 100644
--- a/include/swaynag/swaynag.h
+++ b/include/swaynag/swaynag.h
@@ -58,7 +58,7 @@ struct swaynag_details {
58 int offset; 58 int offset;
59 int visible_lines; 59 int visible_lines;
60 int total_lines; 60 int total_lines;
61 struct swaynag_button button_details; 61 struct swaynag_button *button_details;
62 struct swaynag_button button_up; 62 struct swaynag_button button_up;
63 struct swaynag_button button_down; 63 struct swaynag_button button_down;
64}; 64};
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/move.c b/sway/commands/move.c
index e788d32f..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,
@@ -236,7 +671,6 @@ static void workspace_move_to_output(struct sway_container *workspace,
236 seat_get_focus_inactive(seat, output); 671 seat_get_focus_inactive(seat, output);
237 672
238 container_add_child(output, workspace); 673 container_add_child(output, workspace);
239 wl_signal_emit(&workspace->events.reparent, old_output);
240 674
241 // If moving the last workspace from the old output, create a new workspace 675 // If moving the last workspace from the old output, create a new workspace
242 // on the old output 676 // on the old output
@@ -248,7 +682,7 @@ static void workspace_move_to_output(struct sway_container *workspace,
248 } 682 }
249 683
250 // Try to remove an empty workspace from the destination output. 684 // Try to remove an empty workspace from the destination output.
251 container_reap_empty_recursive(new_output_focus); 685 container_reap_empty(new_output_focus);
252 686
253 output_sort_workspaces(output); 687 output_sort_workspaces(output);
254 seat_set_focus(seat, output); 688 seat_set_focus(seat, output);
@@ -360,7 +794,8 @@ static struct cmd_results *move_in_direction(struct sway_container *container,
360 794
361static const char *expected_position_syntax = 795static const char *expected_position_syntax =
362 "Expected 'move [absolute] position <x> [px] <y> [px]' or " 796 "Expected 'move [absolute] position <x> [px] <y> [px]' or "
363 "'move [absolute] position center|mouse'"; 797 "'move [absolute] position center' or "
798 "'move position cursor|mouse|pointer'";
364 799
365static struct cmd_results *move_to_position(struct sway_container *container, 800static struct cmd_results *move_to_position(struct sway_container *container,
366 int argc, char **argv) { 801 int argc, char **argv) {
@@ -372,7 +807,10 @@ static struct cmd_results *move_to_position(struct sway_container *container,
372 if (!argc) { 807 if (!argc) {
373 return cmd_results_new(CMD_FAILURE, "move", expected_position_syntax); 808 return cmd_results_new(CMD_FAILURE, "move", expected_position_syntax);
374 } 809 }
810
811 bool absolute = false;
375 if (strcmp(argv[0], "absolute") == 0) { 812 if (strcmp(argv[0], "absolute") == 0) {
813 absolute = true;
376 --argc; 814 --argc;
377 ++argv; 815 ++argv;
378 } 816 }
@@ -386,7 +824,8 @@ static struct cmd_results *move_to_position(struct sway_container *container,
386 if (!argc) { 824 if (!argc) {
387 return cmd_results_new(CMD_FAILURE, "move", expected_position_syntax); 825 return cmd_results_new(CMD_FAILURE, "move", expected_position_syntax);
388 } 826 }
389 if (strcmp(argv[0], "mouse") == 0) { 827 if (strcmp(argv[0], "cursor") == 0 || strcmp(argv[0], "mouse") == 0 ||
828 strcmp(argv[0], "pointer") == 0) {
390 struct sway_seat *seat = config->handler_context.seat; 829 struct sway_seat *seat = config->handler_context.seat;
391 if (!seat->cursor) { 830 if (!seat->cursor) {
392 return cmd_results_new(CMD_FAILURE, "move", "No cursor device"); 831 return cmd_results_new(CMD_FAILURE, "move", "No cursor device");
@@ -396,9 +835,15 @@ static struct cmd_results *move_to_position(struct sway_container *container,
396 container_floating_move_to(container, lx, ly); 835 container_floating_move_to(container, lx, ly);
397 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 836 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
398 } else if (strcmp(argv[0], "center") == 0) { 837 } else if (strcmp(argv[0], "center") == 0) {
399 struct sway_container *ws = container_parent(container, C_WORKSPACE); 838 double lx, ly;
400 double lx = ws->x + (ws->width - container->width) / 2; 839 if (absolute) {
401 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 }
402 container_floating_move_to(container, lx, ly); 847 container_floating_move_to(container, lx, ly);
403 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 848 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
404 } 849 }
@@ -430,6 +875,11 @@ static struct cmd_results *move_to_position(struct sway_container *container,
430 "Invalid position specified"); 875 "Invalid position specified");
431 } 876 }
432 877
878 if (!absolute) {
879 struct sway_container *ws = container_parent(container, C_WORKSPACE);
880 lx += ws->x;
881 ly += ws->y;
882 }
433 container_floating_move_to(container, lx, ly); 883 container_floating_move_to(container, lx, ly);
434 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 884 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
435} 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 0f3005f4..ad659ef5 100644
--- a/sway/commands/resize.c
+++ b/sway/commands/resize.c
@@ -5,6 +5,7 @@
5#include <stdlib.h> 5#include <stdlib.h>
6#include <string.h> 6#include <string.h>
7#include <strings.h> 7#include <strings.h>
8#include <wlr/util/edges.h>
8#include <wlr/util/log.h> 9#include <wlr/util/log.h>
9#include "sway/commands.h" 10#include "sway/commands.h"
10#include "sway/tree/arrange.h" 11#include "sway/tree/arrange.h"
@@ -158,6 +159,27 @@ static int parallel_size(struct sway_container *c, enum resize_axis a) {
158 return normalize_axis(a) == RESIZE_AXIS_HORIZONTAL ? c->width : c->height; 159 return normalize_axis(a) == RESIZE_AXIS_HORIZONTAL ? c->width : c->height;
159} 160}
160 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
161static void resize_tiled(struct sway_container *parent, int amount, 183static void resize_tiled(struct sway_container *parent, int amount,
162 enum resize_axis axis) { 184 enum resize_axis axis) {
163 struct sway_container *focused = parent; 185 struct sway_container *focused = parent;
@@ -250,10 +272,10 @@ static void resize_tiled(struct sway_container *parent, int amount,
250 } 272 }
251 } 273 }
252 274
253 enum resize_edge minor_edge = axis == RESIZE_AXIS_HORIZONTAL ? 275 enum wlr_edges minor_edge = axis == RESIZE_AXIS_HORIZONTAL ?
254 RESIZE_EDGE_LEFT : RESIZE_EDGE_TOP; 276 WLR_EDGE_LEFT : WLR_EDGE_TOP;
255 enum resize_edge major_edge = axis == RESIZE_AXIS_HORIZONTAL ? 277 enum wlr_edges major_edge = axis == RESIZE_AXIS_HORIZONTAL ?
256 RESIZE_EDGE_RIGHT : RESIZE_EDGE_BOTTOM; 278 WLR_EDGE_RIGHT : WLR_EDGE_BOTTOM;
257 279
258 for (int i = 0; i < parent->parent->children->length; i++) { 280 for (int i = 0; i < parent->parent->children->length; i++) {
259 struct sway_container *sibling = parent->parent->children->items[i]; 281 struct sway_container *sibling = parent->parent->children->items[i];
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 0cb499e7..2768cf58 100644
--- a/sway/debug-tree.c
+++ b/sway/debug-tree.c
@@ -3,15 +3,19 @@
3#include <wlr/render/wlr_texture.h> 3#include <wlr/render/wlr_texture.h>
4#include <wlr/util/log.h> 4#include <wlr/util/log.h>
5#include "config.h" 5#include "config.h"
6#include "sway/debug.h"
6#include "sway/input/input-manager.h" 7#include "sway/input/input-manager.h"
7#include "sway/input/seat.h" 8#include "sway/input/seat.h"
9#include "sway/output.h"
8#include "sway/server.h" 10#include "sway/server.h"
9#include "sway/tree/container.h" 11#include "sway/tree/container.h"
10#include "sway/tree/layout.h" 12#include "sway/tree/root.h"
11#include "cairo.h" 13#include "cairo.h"
12#include "config.h" 14#include "config.h"
13#include "pango.h" 15#include "pango.h"
14 16
17struct sway_debug debug;
18
15static const char *layout_to_str(enum sway_container_layout layout) { 19static const char *layout_to_str(enum sway_container_layout layout) {
16 switch (layout) { 20 switch (layout) {
17 case L_HORIZ: 21 case L_HORIZ:
@@ -67,10 +71,8 @@ static int draw_container(cairo_t *cairo, struct sway_container *container,
67 return height; 71 return height;
68} 72}
69 73
70bool enable_debug_tree = false;
71
72void update_debug_tree() { 74void update_debug_tree() {
73 if (!enable_debug_tree) { 75 if (!debug.render_tree) {
74 return; 76 return;
75 } 77 }
76 78
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 2253eb51..bbebe453 100644
--- a/sway/desktop/output.c
+++ b/sway/desktop/output.c
@@ -23,7 +23,7 @@
23#include "sway/server.h" 23#include "sway/server.h"
24#include "sway/tree/arrange.h" 24#include "sway/tree/arrange.h"
25#include "sway/tree/container.h" 25#include "sway/tree/container.h"
26#include "sway/tree/layout.h" 26#include "sway/tree/root.h"
27#include "sway/tree/view.h" 27#include "sway/tree/view.h"
28#include "sway/tree/workspace.h" 28#include "sway/tree/workspace.h"
29 29
@@ -163,8 +163,10 @@ void output_view_for_each_popup(struct sway_output *output,
163 .user_iterator = iterator, 163 .user_iterator = iterator,
164 .user_data = user_data, 164 .user_data = user_data,
165 .output = output, 165 .output = output,
166 .ox = view->swayc->current.view_x - output->swayc->current.swayc_x, 166 .ox = view->swayc->current.view_x - output->swayc->current.swayc_x
167 .oy = view->swayc->current.view_y - output->swayc->current.swayc_y, 167 - view->geometry.x,
168 .oy = view->swayc->current.view_y - output->swayc->current.swayc_y
169 - view->geometry.y,
168 .width = view->swayc->current.view_width, 170 .width = view->swayc->current.view_width,
169 .height = view->swayc->current.view_height, 171 .height = view->swayc->current.view_height,
170 .rotation = 0, // TODO 172 .rotation = 0, // TODO
@@ -496,7 +498,7 @@ void output_damage_whole_container(struct sway_output *output,
496static void damage_handle_destroy(struct wl_listener *listener, void *data) { 498static void damage_handle_destroy(struct wl_listener *listener, void *data) {
497 struct sway_output *output = 499 struct sway_output *output =
498 wl_container_of(listener, output, damage_destroy); 500 wl_container_of(listener, output, damage_destroy);
499 container_destroy(output->swayc); 501 output_begin_destroy(output->swayc);
500} 502}
501 503
502static void handle_destroy(struct wl_listener *listener, void *data) { 504static void handle_destroy(struct wl_listener *listener, void *data) {
@@ -504,7 +506,7 @@ static void handle_destroy(struct wl_listener *listener, void *data) {
504 wl_signal_emit(&output->events.destroy, output); 506 wl_signal_emit(&output->events.destroy, output);
505 507
506 if (output->swayc) { 508 if (output->swayc) {
507 container_destroy(output->swayc); 509 output_begin_destroy(output->swayc);
508 } 510 }
509 511
510 wl_list_remove(&output->link); 512 wl_list_remove(&output->link);
@@ -554,7 +556,7 @@ void handle_new_output(struct wl_listener *listener, void *data) {
554 wl_signal_add(&wlr_output->events.destroy, &output->destroy); 556 wl_signal_add(&wlr_output->events.destroy, &output->destroy);
555 output->destroy.notify = handle_destroy; 557 output->destroy.notify = handle_destroy;
556 558
557 wl_list_insert(&root_container.sway_root->outputs, &output->link); 559 wl_list_insert(&root_container.sway_root->all_outputs, &output->link);
558 560
559 if (!wl_list_empty(&wlr_output->modes)) { 561 if (!wl_list_empty(&wlr_output->modes)) {
560 struct wlr_output_mode *mode = 562 struct wlr_output_mode *mode =
diff --git a/sway/desktop/render.c b/sway/desktop/render.c
index aa70903e..b5a10370 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
@@ -813,8 +813,6 @@ static void render_floating(struct sway_output *soutput,
813 } 813 }
814} 814}
815 815
816const char *damage_debug = NULL;
817
818void output_render(struct sway_output *output, struct timespec *when, 816void output_render(struct sway_output *output, struct timespec *when,
819 pixman_region32_t *damage) { 817 pixman_region32_t *damage) {
820 struct wlr_output *wlr_output = output->wlr_output; 818 struct wlr_output *wlr_output = output->wlr_output;
@@ -828,21 +826,17 @@ void output_render(struct sway_output *output, struct timespec *when,
828 826
829 wlr_renderer_begin(renderer, wlr_output->width, wlr_output->height); 827 wlr_renderer_begin(renderer, wlr_output->width, wlr_output->height);
830 828
831 bool damage_whole_before_swap = false;
832 if (!pixman_region32_not_empty(damage)) { 829 if (!pixman_region32_not_empty(damage)) {
833 // Output isn't damaged but needs buffer swap 830 // Output isn't damaged but needs buffer swap
834 goto renderer_end; 831 goto renderer_end;
835 } 832 }
836 833
837 if (damage_debug != NULL) { 834 if (debug.damage == DAMAGE_HIGHLIGHT) {
838 if (strcmp(damage_debug, "highlight") == 0) { 835 wlr_renderer_clear(renderer, (float[]){1, 1, 0, 1});
839 wlr_renderer_clear(renderer, (float[]){1, 1, 0, 1}); 836 } else if (debug.damage == DAMAGE_RERENDER) {
840 damage_whole_before_swap = true; 837 int width, height;
841 } else if (strcmp(damage_debug, "rerender") == 0) { 838 wlr_output_transformed_resolution(wlr_output, &width, &height);
842 int width, height; 839 pixman_region32_union_rect(damage, damage, 0, 0, width, height);
843 wlr_output_transformed_resolution(wlr_output, &width, &height);
844 pixman_region32_union_rect(damage, damage, 0, 0, width, height);
845 }
846 } 840 }
847 841
848 struct sway_container *workspace = output_get_active_workspace(output); 842 struct sway_container *workspace = output_get_active_workspace(output);
@@ -916,12 +910,12 @@ render_overlay:
916 render_drag_icons(output, damage, &root_container.sway_root->drag_icons); 910 render_drag_icons(output, damage, &root_container.sway_root->drag_icons);
917 911
918renderer_end: 912renderer_end:
919 if (root_container.sway_root->debug_tree) { 913 if (debug.render_tree) {
914 wlr_renderer_scissor(renderer, NULL);
920 wlr_render_texture(renderer, root_container.sway_root->debug_tree, 915 wlr_render_texture(renderer, root_container.sway_root->debug_tree,
921 wlr_output->transform_matrix, 0, 0, 1); 916 wlr_output->transform_matrix, 0, 40, 1);
922 } 917 }
923 918 if (debug.damage == DAMAGE_HIGHLIGHT) {
924 if (damage_whole_before_swap || root_container.sway_root->debug_tree) {
925 int width, height; 919 int width, height;
926 wlr_output_transformed_resolution(wlr_output, &width, &height); 920 wlr_output_transformed_resolution(wlr_output, &width, &height);
927 pixman_region32_union_rect(damage, damage, 0, 0, width, height); 921 pixman_region32_union_rect(damage, damage, 0, 0, width, height);
diff --git a/sway/desktop/transaction.c b/sway/desktop/transaction.c
index 692fb447..c18529fb 100644
--- a/sway/desktop/transaction.c
+++ b/sway/desktop/transaction.c
@@ -1,5 +1,6 @@
1#define _POSIX_C_SOURCE 200809L 1#define _POSIX_C_SOURCE 200809L
2#include <errno.h> 2#include <errno.h>
3#include <limits.h>
3#include <stdbool.h> 4#include <stdbool.h>
4#include <stdlib.h> 5#include <stdlib.h>
5#include <string.h> 6#include <string.h>
@@ -16,26 +17,12 @@
16#include "list.h" 17#include "list.h"
17#include "log.h" 18#include "log.h"
18 19
19/**
20 * How long we should wait for views to respond to the configure before giving
21 * up and applying the transaction anyway.
22 */
23int txn_timeout_ms = 200;
24
25/**
26 * If enabled, sway will always wait for the transaction timeout before
27 * applying it, rather than applying it when the views are ready. This allows us
28 * to observe the rendered state while a transaction is in progress.
29 */
30bool txn_debug = false;
31
32struct sway_transaction { 20struct sway_transaction {
33 struct wl_event_source *timer; 21 struct wl_event_source *timer;
34 list_t *instructions; // struct sway_transaction_instruction * 22 list_t *instructions; // struct sway_transaction_instruction *
35 size_t num_waiting; 23 size_t num_waiting;
36 size_t num_configures; 24 size_t num_configures;
37 uint32_t con_ids; // Bitwise XOR of view container IDs 25 uint32_t con_ids; // Bitwise XOR of view container IDs
38 struct timespec create_time;
39 struct timespec commit_time; 26 struct timespec commit_time;
40}; 27};
41 28
@@ -53,9 +40,6 @@ static struct sway_transaction *transaction_create() {
53 return NULL; 40 return NULL;
54 } 41 }
55 transaction->instructions = create_list(); 42 transaction->instructions = create_list();
56 if (server.debug_txn_timings) {
57 clock_gettime(CLOCK_MONOTONIC, &transaction->create_time);
58 }
59 return transaction; 43 return transaction;
60} 44}
61 45
@@ -70,7 +54,22 @@ static void transaction_destroy(struct sway_transaction *transaction) {
70 con->instruction = NULL; 54 con->instruction = NULL;
71 } 55 }
72 if (con->destroying && con->ntxnrefs == 0) { 56 if (con->destroying && con->ntxnrefs == 0) {
73 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 }
74 } 73 }
75 free(instruction); 74 free(instruction);
76 } 75 }
@@ -150,19 +149,14 @@ static void transaction_add_container(struct sway_transaction *transaction,
150 */ 149 */
151static void transaction_apply(struct sway_transaction *transaction) { 150static void transaction_apply(struct sway_transaction *transaction) {
152 wlr_log(WLR_DEBUG, "Applying transaction %p", transaction); 151 wlr_log(WLR_DEBUG, "Applying transaction %p", transaction);
153 if (server.debug_txn_timings) { 152 if (debug.txn_timings) {
154 struct timespec now; 153 struct timespec now;
155 clock_gettime(CLOCK_MONOTONIC, &now); 154 clock_gettime(CLOCK_MONOTONIC, &now);
156 struct timespec *create = &transaction->create_time;
157 struct timespec *commit = &transaction->commit_time; 155 struct timespec *commit = &transaction->commit_time;
158 float ms_arranging = (commit->tv_sec - create->tv_sec) * 1000 + 156 float ms = (now.tv_sec - commit->tv_sec) * 1000 +
159 (commit->tv_nsec - create->tv_nsec) / 1000000.0;
160 float ms_waiting = (now.tv_sec - commit->tv_sec) * 1000 +
161 (now.tv_nsec - commit->tv_nsec) / 1000000.0; 157 (now.tv_nsec - commit->tv_nsec) / 1000000.0;
162 float ms_total = ms_arranging + ms_waiting; 158 wlr_log(WLR_DEBUG, "Transaction %p: %.1fms waiting "
163 wlr_log(WLR_DEBUG, "Transaction %p: %.1fms arranging, %.1fms waiting, " 159 "(%.1f frames if 60Hz)", transaction, ms, ms / (1000.0f / 60));
164 "%.1fms total (%.1f frames if 60Hz)", transaction,
165 ms_arranging, ms_waiting, ms_total, ms_total / (1000.0f / 60));
166 } 160 }
167 161
168 // Apply the instruction state to the container's current state 162 // Apply the instruction state to the container's current state
@@ -196,7 +190,9 @@ static void transaction_apply(struct sway_transaction *transaction) {
196 sizeof(struct sway_container_state)); 190 sizeof(struct sway_container_state));
197 191
198 if (container->type == C_VIEW && container->sway_view->saved_buffer) { 192 if (container->type == C_VIEW && container->sway_view->saved_buffer) {
199 view_remove_saved_buffer(container->sway_view); 193 if (!container->destroying || container->ntxnrefs == 1) {
194 view_remove_saved_buffer(container->sway_view);
195 }
200 } 196 }
201 197
202 // Damage the new location 198 // Damage the new location
@@ -214,6 +210,9 @@ static void transaction_apply(struct sway_transaction *transaction) {
214 } 210 }
215 211
216 container->instruction = NULL; 212 container->instruction = NULL;
213 if (container->type == C_CONTAINER || container->type == C_VIEW) {
214 container_discover_outputs(container);
215 }
217 } 216 }
218} 217}
219 218
@@ -304,7 +303,7 @@ static void transaction_commit(struct sway_transaction *transaction) {
304 struct timespec when; 303 struct timespec when;
305 wlr_surface_send_frame_done(con->sway_view->surface, &when); 304 wlr_surface_send_frame_done(con->sway_view->surface, &when);
306 } 305 }
307 if (con->type == C_VIEW) { 306 if (con->type == C_VIEW && !con->sway_view->saved_buffer) {
308 view_save_buffer(con->sway_view); 307 view_save_buffer(con->sway_view);
309 memcpy(&con->sway_view->saved_geometry, &con->sway_view->geometry, 308 memcpy(&con->sway_view->saved_geometry, &con->sway_view->geometry,
310 sizeof(struct wlr_box)); 309 sizeof(struct wlr_box));
@@ -312,25 +311,30 @@ static void transaction_commit(struct sway_transaction *transaction) {
312 con->instruction = instruction; 311 con->instruction = instruction;
313 } 312 }
314 transaction->num_configures = transaction->num_waiting; 313 transaction->num_configures = transaction->num_waiting;
315 if (server.debug_txn_timings) { 314 if (debug.txn_timings) {
316 clock_gettime(CLOCK_MONOTONIC, &transaction->commit_time); 315 clock_gettime(CLOCK_MONOTONIC, &transaction->commit_time);
317 } 316 }
317 if (debug.noatomic) {
318 transaction->num_waiting = 0;
319 } else if (debug.txn_wait) {
320 // Force the transaction to time out even if all views are ready.
321 // We do this by inflating the waiting counter.
322 transaction->num_waiting += 1000000;
323 }
318 324
319 if (transaction->num_waiting) { 325 if (transaction->num_waiting) {
320 // Set up a timer which the views must respond within 326 // Set up a timer which the views must respond within
321 transaction->timer = wl_event_loop_add_timer(server.wl_event_loop, 327 transaction->timer = wl_event_loop_add_timer(server.wl_event_loop,
322 handle_timeout, transaction); 328 handle_timeout, transaction);
323 if (transaction->timer) { 329 if (transaction->timer) {
324 wl_event_source_timer_update(transaction->timer, txn_timeout_ms); 330 wl_event_source_timer_update(transaction->timer,
331 server.txn_timeout_ms);
325 } else { 332 } else {
326 wlr_log(WLR_ERROR, "Unable to create transaction timer (%s). " 333 wlr_log(WLR_ERROR, "Unable to create transaction timer (%s). "
327 "Some imperfect frames might be rendered.", 334 "Some imperfect frames might be rendered.",
328 strerror(errno)); 335 strerror(errno));
329 handle_timeout(transaction); 336 transaction->num_waiting = 0;
330 } 337 }
331 } else {
332 wlr_log(WLR_DEBUG,
333 "Transaction %p has nothing to wait for", transaction);
334 } 338 }
335 339
336 // The debug tree shows the pending/live tree. Here is a good place to 340 // The debug tree shows the pending/live tree. Here is a good place to
@@ -343,7 +347,7 @@ static void set_instruction_ready(
343 struct sway_transaction_instruction *instruction) { 347 struct sway_transaction_instruction *instruction) {
344 struct sway_transaction *transaction = instruction->transaction; 348 struct sway_transaction *transaction = instruction->transaction;
345 349
346 if (server.debug_txn_timings) { 350 if (debug.txn_timings) {
347 struct timespec now; 351 struct timespec now;
348 clock_gettime(CLOCK_MONOTONIC, &now); 352 clock_gettime(CLOCK_MONOTONIC, &now);
349 struct timespec *start = &transaction->commit_time; 353 struct timespec *start = &transaction->commit_time;
@@ -354,21 +358,16 @@ static void set_instruction_ready(
354 transaction->num_configures - transaction->num_waiting + 1, 358 transaction->num_configures - transaction->num_waiting + 1,
355 transaction->num_configures, ms, 359 transaction->num_configures, ms,
356 instruction->container->name); 360 instruction->container->name);
357
358 } 361 }
359 362
360 // If the transaction has timed out then its num_waiting will be 0 already. 363 // If the transaction has timed out then its num_waiting will be 0 already.
361 if (transaction->num_waiting > 0 && --transaction->num_waiting == 0) { 364 if (transaction->num_waiting > 0 && --transaction->num_waiting == 0) {
362 if (!txn_debug) { 365 wlr_log(WLR_DEBUG, "Transaction %p is ready", transaction);
363 wlr_log(WLR_DEBUG, "Transaction %p is ready", transaction); 366 wl_event_source_timer_update(transaction->timer, 0);
364 wl_event_source_timer_update(transaction->timer, 0);
365 }
366 } 367 }
367 368
368 instruction->container->instruction = NULL; 369 instruction->container->instruction = NULL;
369 if (!txn_debug) { 370 transaction_progress_queue();
370 transaction_progress_queue();
371 }
372} 371}
373 372
374void transaction_notify_view_ready_by_serial(struct sway_view *view, 373void transaction_notify_view_ready_by_serial(struct sway_view *view,
diff --git a/sway/desktop/xdg_shell.c b/sway/desktop/xdg_shell.c
index aae129bd..7d1824f1 100644
--- a/sway/desktop/xdg_shell.c
+++ b/sway/desktop/xdg_shell.c
@@ -13,7 +13,6 @@
13#include "sway/server.h" 13#include "sway/server.h"
14#include "sway/tree/arrange.h" 14#include "sway/tree/arrange.h"
15#include "sway/tree/container.h" 15#include "sway/tree/container.h"
16#include "sway/tree/layout.h"
17#include "sway/tree/view.h" 16#include "sway/tree/view.h"
18 17
19static const struct sway_view_child_impl popup_impl; 18static const struct sway_view_child_impl popup_impl;
@@ -448,7 +447,7 @@ static void handle_destroy(struct wl_listener *listener, void *data) {
448 wl_list_remove(&xdg_shell_view->map.link); 447 wl_list_remove(&xdg_shell_view->map.link);
449 wl_list_remove(&xdg_shell_view->unmap.link); 448 wl_list_remove(&xdg_shell_view->unmap.link);
450 view->wlr_xdg_surface = NULL; 449 view->wlr_xdg_surface = NULL;
451 view_destroy(view); 450 view_begin_destroy(view);
452} 451}
453 452
454struct sway_view *view_from_wlr_xdg_surface( 453struct 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..522fddca 100644
--- a/sway/desktop/xdg_shell_v6.c
+++ b/sway/desktop/xdg_shell_v6.c
@@ -12,7 +12,6 @@
12#include "sway/server.h" 12#include "sway/server.h"
13#include "sway/tree/arrange.h" 13#include "sway/tree/arrange.h"
14#include "sway/tree/container.h" 14#include "sway/tree/container.h"
15#include "sway/tree/layout.h"
16#include "sway/tree/view.h" 15#include "sway/tree/view.h"
17 16
18static const struct sway_view_child_impl popup_impl; 17static const struct sway_view_child_impl popup_impl;
@@ -441,7 +440,7 @@ static void handle_destroy(struct wl_listener *listener, void *data) {
441 wl_list_remove(&xdg_shell_v6_view->map.link); 440 wl_list_remove(&xdg_shell_v6_view->map.link);
442 wl_list_remove(&xdg_shell_v6_view->unmap.link); 441 wl_list_remove(&xdg_shell_v6_view->unmap.link);
443 view->wlr_xdg_surface_v6 = NULL; 442 view->wlr_xdg_surface_v6 = NULL;
444 view_destroy(view); 443 view_begin_destroy(view);
445} 444}
446 445
447struct sway_view *view_from_wlr_xdg_surface_v6( 446struct 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 caee37a6..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}
@@ -984,7 +973,7 @@ void seat_begin_resize_floating(struct sway_seat *seat,
984 seat->op_resize_preserve_ratio = keyboard && 973 seat->op_resize_preserve_ratio = keyboard &&
985 (wlr_keyboard_get_modifiers(keyboard) & WLR_MODIFIER_SHIFT); 974 (wlr_keyboard_get_modifiers(keyboard) & WLR_MODIFIER_SHIFT);
986 seat->op_resize_edge = edge == WLR_EDGE_NONE ? 975 seat->op_resize_edge = edge == WLR_EDGE_NONE ?
987 RESIZE_EDGE_BOTTOM | RESIZE_EDGE_RIGHT : edge; 976 WLR_EDGE_BOTTOM | WLR_EDGE_RIGHT : edge;
988 seat->op_button = button; 977 seat->op_button = button;
989 seat->op_ref_lx = seat->cursor->cursor->x; 978 seat->op_ref_lx = seat->cursor->cursor->x;
990 seat->op_ref_ly = seat->cursor->cursor->y; 979 seat->op_ref_ly = seat->cursor->cursor->y;
diff --git a/sway/ipc-json.c b/sway/ipc-json.c
index f40af043..06cb7e11 100644
--- a/sway/ipc-json.c
+++ b/sway/ipc-json.c
@@ -5,6 +5,7 @@
5#include "sway/config.h" 5#include "sway/config.h"
6#include "sway/ipc-json.h" 6#include "sway/ipc-json.h"
7#include "sway/tree/container.h" 7#include "sway/tree/container.h"
8#include "sway/tree/view.h"
8#include "sway/tree/workspace.h" 9#include "sway/tree/workspace.h"
9#include "sway/output.h" 10#include "sway/output.h"
10#include "sway/input/input-manager.h" 11#include "sway/input/input-manager.h"
@@ -192,6 +193,16 @@ static void ipc_json_describe_view(struct sway_container *c, json_object *object
192 c->name ? json_object_new_string(c->name) : NULL); 193 c->name ? json_object_new_string(c->name) : NULL);
193 json_object_object_add(object, "type", json_object_new_string("con")); 194 json_object_object_add(object, "type", json_object_new_string("con"));
194 195
196 if (c->type == C_VIEW) {
197 const char *app_id = view_get_app_id(c->sway_view);
198 json_object_object_add(object, "app_id",
199 app_id ? json_object_new_string(app_id) : NULL);
200
201 const char *class = view_get_class(c->sway_view);
202 json_object_object_add(object, "class",
203 class ? json_object_new_string(class) : NULL);
204 }
205
195 if (c->parent) { 206 if (c->parent) {
196 json_object_object_add(object, "layout", 207 json_object_object_add(object, "layout",
197 json_object_new_string(ipc_json_layout_description(c->layout))); 208 json_object_new_string(ipc_json_layout_description(c->layout)));
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 54f48340..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"
@@ -235,14 +235,20 @@ static void drop_permissions(bool keep_caps) {
235} 235}
236 236
237void enable_debug_flag(const char *flag) { 237void enable_debug_flag(const char *flag) {
238 if (strcmp(flag, "render-tree") == 0) { 238 if (strcmp(flag, "damage=highlight") == 0) {
239 enable_debug_tree = true; 239 debug.damage = DAMAGE_HIGHLIGHT;
240 } else if (strncmp(flag, "damage=", 7) == 0) { 240 } else if (strcmp(flag, "damage=rerender") == 0) {
241 damage_debug = &flag[7]; 241 debug.damage = DAMAGE_RERENDER;
242 } else if (strcmp(flag, "txn-debug") == 0) { 242 } else if (strcmp(flag, "noatomic") == 0) {
243 txn_debug = true; 243 debug.noatomic = true;
244 } else if (strcmp(flag, "render-tree") == 0) {
245 debug.render_tree = true;
246 } else if (strcmp(flag, "txn-wait") == 0) {
247 debug.txn_wait = true;
248 } else if (strcmp(flag, "txn-timings") == 0) {
249 debug.txn_timings = true;
244 } else if (strncmp(flag, "txn-timeout=", 12) == 0) { 250 } else if (strncmp(flag, "txn-timeout=", 12) == 0) {
245 txn_timeout_ms = atoi(&flag[12]); 251 server.txn_timeout_ms = atoi(&flag[12]);
246 } 252 }
247} 253}
248 254
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 e8dc63be..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"
@@ -128,10 +128,11 @@ bool server_init(struct sway_server *server) {
128 return false; 128 return false;
129 } 129 }
130 130
131 const char *debug = getenv("SWAY_DEBUG"); 131 // This may have been set already via -Dtxn-timeout
132 if (debug != NULL && strcmp(debug, "txn_timings") == 0) { 132 if (!server->txn_timeout_ms) {
133 server->debug_txn_timings = true; 133 server->txn_timeout_ms = 200;
134 } 134 }
135
135 server->dirty_containers = create_list(); 136 server->dirty_containers = create_list();
136 server->transactions = create_list(); 137 server->transactions = create_list();
137 138
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 cf4a5d9a..60e5b951 100644
--- a/sway/tree/arrange.c
+++ b/sway/tree/arrange.c
@@ -7,7 +7,6 @@
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"
@@ -39,7 +38,7 @@ static void apply_horiz_layout(struct sway_container *parent) {
39 child->width = parent->width; 38 child->width = parent->width;
40 } 39 }
41 } 40 }
42 remove_gaps(child); 41 container_remove_gaps(child);
43 total_width += child->width; 42 total_width += child->width;
44 } 43 }
45 double scale = parent->width / total_width; 44 double scale = parent->width / total_width;
@@ -62,7 +61,7 @@ static void apply_horiz_layout(struct sway_container *parent) {
62 if (i == num_children - 1) { 61 if (i == num_children - 1) {
63 child->width = parent->x + parent->width - child->x; 62 child->width = parent->x + parent->width - child->x;
64 } 63 }
65 add_gaps(child); 64 container_add_gaps(child);
66 } 65 }
67} 66}
68 67
@@ -91,7 +90,7 @@ static void apply_vert_layout(struct sway_container *parent) {
91 child->height = parent_height; 90 child->height = parent_height;
92 } 91 }
93 } 92 }
94 remove_gaps(child); 93 container_remove_gaps(child);
95 total_height += child->height; 94 total_height += child->height;
96 } 95 }
97 double scale = parent_height / total_height; 96 double scale = parent_height / total_height;
@@ -115,7 +114,7 @@ static void apply_vert_layout(struct sway_container *parent) {
115 child->height = 114 child->height =
116 parent->y + parent_offset + parent_height - child->y; 115 parent->y + parent_offset + parent_height - child->y;
117 } 116 }
118 add_gaps(child); 117 container_add_gaps(child);
119 } 118 }
120} 119}
121 120
@@ -133,12 +132,12 @@ static void apply_tabbed_or_stacked_layout(struct sway_container *parent) {
133 size_t parent_height = parent->height - parent_offset; 132 size_t parent_height = parent->height - parent_offset;
134 for (int i = 0; i < parent->children->length; ++i) { 133 for (int i = 0; i < parent->children->length; ++i) {
135 struct sway_container *child = parent->children->items[i]; 134 struct sway_container *child = parent->children->items[i];
136 remove_gaps(child); 135 container_remove_gaps(child);
137 child->x = parent->x; 136 child->x = parent->x;
138 child->y = parent->y + parent_offset; 137 child->y = parent->y + parent_offset;
139 child->width = parent->width; 138 child->width = parent->width;
140 child->height = parent_height; 139 child->height = parent_height;
141 add_gaps(child); 140 container_add_gaps(child);
142 } 141 }
143} 142}
144 143
@@ -205,12 +204,32 @@ static void arrange_workspace(struct sway_container *workspace) {
205 struct wlr_box *area = &output->sway_output->usable_area; 204 struct wlr_box *area = &output->sway_output->usable_area;
206 wlr_log(WLR_DEBUG, "Usable area for ws: %dx%d@%d,%d", 205 wlr_log(WLR_DEBUG, "Usable area for ws: %dx%d@%d,%d",
207 area->width, area->height, area->x, area->y); 206 area->width, area->height, area->x, area->y);
208 remove_gaps(workspace); 207 workspace_remove_gaps(workspace);
208
209 double prev_x = workspace->x;
210 double prev_y = workspace->y;
209 workspace->width = area->width; 211 workspace->width = area->width;
210 workspace->height = area->height; 212 workspace->height = area->height;
211 workspace->x = output->x + area->x; 213 workspace->x = output->x + area->x;
212 workspace->y = output->y + area->y; 214 workspace->y = output->y + area->y;
213 add_gaps(workspace); 215
216 // Adjust any floating containers
217 double diff_x = workspace->x - prev_x;
218 double diff_y = workspace->y - prev_y;
219 for (int i = 0; i < workspace->sway_workspace->floating->length; ++i) {
220 struct sway_container *floater =
221 workspace->sway_workspace->floating->items[i];
222 container_floating_translate(floater, diff_x, diff_y);
223 double center_x = floater->x + floater->width / 2;
224 double center_y = floater->y + floater->height / 2;
225 struct wlr_box workspace_box;
226 container_get_box(workspace, &workspace_box);
227 if (!wlr_box_contains_point(&workspace_box, center_x, center_y)) {
228 container_floating_move_to_center(floater);
229 }
230 }
231
232 workspace_add_gaps(workspace);
214 container_set_dirty(workspace); 233 container_set_dirty(workspace);
215 wlr_log(WLR_DEBUG, "Arranging workspace '%s' at %f, %f", workspace->name, 234 wlr_log(WLR_DEBUG, "Arranging workspace '%s' at %f, %f", workspace->name,
216 workspace->x, workspace->y); 235 workspace->x, workspace->y);
@@ -294,41 +313,3 @@ void arrange_windows(struct sway_container *container) {
294 break; 313 break;
295 } 314 }
296} 315}
297
298void remove_gaps(struct sway_container *c) {
299 if (c->current_gaps == 0) {
300 wlr_log(WLR_DEBUG, "Removing gaps: not gapped: %p", c);
301 return;
302 }
303
304 c->width += c->current_gaps * 2;
305 c->height += c->current_gaps * 2;
306 c->x -= c->current_gaps;
307 c->y -= c->current_gaps;
308
309 c->current_gaps = 0;
310
311 wlr_log(WLR_DEBUG, "Removing gaps %p", c);
312}
313
314void add_gaps(struct sway_container *c) {
315 if (c->current_gaps > 0 || c->type == C_CONTAINER) {
316 wlr_log(WLR_DEBUG, "Not adding gaps: %p", c);
317 return;
318 }
319
320 if (c->type == C_WORKSPACE &&
321 !(config->edge_gaps || (config->smart_gaps && c->children->length > 1))) {
322 return;
323 }
324
325 double gaps = c->has_gaps ? c->gaps_inner : config->gaps_inner;
326
327 c->x += gaps;
328 c->y += gaps;
329 c->width -= 2 * gaps;
330 c->height -= 2 * gaps;
331 c->current_gaps = gaps;
332
333 wlr_log(WLR_DEBUG, "Adding gaps: %p", c);
334}
diff --git a/sway/tree/container.c b/sway/tree/container.c
index ff947ca8..04454ab6 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) {
@@ -76,31 +73,6 @@ void container_update_textures_recursive(struct sway_container *con) {
76 } 73 }
77} 74}
78 75
79static void handle_reparent(struct wl_listener *listener,
80 void *data) {
81 struct sway_container *container =
82 wl_container_of(listener, container, reparent);
83 struct sway_container *old_parent = data;
84
85 struct sway_container *old_output = old_parent;
86 if (old_output != NULL && old_output->type != C_OUTPUT) {
87 old_output = container_parent(old_output, C_OUTPUT);
88 }
89
90 struct sway_container *new_output = container->parent;
91 if (new_output != NULL && new_output->type != C_OUTPUT) {
92 new_output = container_parent(new_output, C_OUTPUT);
93 }
94
95 if (old_output && new_output) {
96 float old_scale = old_output->sway_output->wlr_output->scale;
97 float new_scale = new_output->sway_output->wlr_output->scale;
98 if (old_scale != new_scale) {
99 container_update_textures_recursive(container);
100 }
101 }
102}
103
104struct sway_container *container_create(enum sway_container_type type) { 76struct sway_container *container_create(enum sway_container_type type) {
105 // next id starts at 1 because 0 is assigned to root_container in layout.c 77 // next id starts at 1 because 0 is assigned to root_container in layout.c
106 static size_t next_id = 1; 78 static size_t next_id = 1;
@@ -117,12 +89,9 @@ struct sway_container *container_create(enum sway_container_type type) {
117 c->children = create_list(); 89 c->children = create_list();
118 c->current.children = create_list(); 90 c->current.children = create_list();
119 } 91 }
92 c->outputs = create_list();
120 93
121 wl_signal_init(&c->events.destroy); 94 wl_signal_init(&c->events.destroy);
122 wl_signal_init(&c->events.reparent);
123
124 wl_signal_add(&c->events.reparent, &c->reparent);
125 c->reparent.notify = handle_reparent;
126 95
127 c->has_gaps = false; 96 c->has_gaps = false;
128 c->gaps_inner = 0; 97 c->gaps_inner = 0;
@@ -132,199 +101,53 @@ struct sway_container *container_create(enum sway_container_type type) {
132 return c; 101 return c;
133} 102}
134 103
135static void container_workspace_free(struct sway_workspace *ws) { 104void container_destroy(struct sway_container *con) {
136 list_foreach(ws->output_priority, free); 105 if (!sway_assert(con->type == C_CONTAINER || con->type == C_VIEW,
137 list_free(ws->output_priority); 106 "Expected a container or view")) {
138 list_free(ws->floating); 107 return;
139 free(ws); 108 }
140} 109 if (!sway_assert(con->destroying,
141
142void container_free(struct sway_container *cont) {
143 if (!sway_assert(cont->destroying,
144 "Tried to free container which wasn't marked as destroying")) { 110 "Tried to free container which wasn't marked as destroying")) {
145 return; 111 return;
146 } 112 }
147 if (!sway_assert(cont->ntxnrefs == 0, "Tried to free container " 113 if (!sway_assert(con->ntxnrefs == 0, "Tried to free container "
148 "which is still referenced by transactions")) { 114 "which is still referenced by transactions")) {
149 return; 115 return;
150 } 116 }
151 free(cont->name); 117 free(con->name);
152 free(cont->formatted_title); 118 free(con->formatted_title);
153 wlr_texture_destroy(cont->title_focused); 119 wlr_texture_destroy(con->title_focused);
154 wlr_texture_destroy(cont->title_focused_inactive); 120 wlr_texture_destroy(con->title_focused_inactive);
155 wlr_texture_destroy(cont->title_unfocused); 121 wlr_texture_destroy(con->title_unfocused);
156 wlr_texture_destroy(cont->title_urgent); 122 wlr_texture_destroy(con->title_urgent);
157 list_free(cont->children); 123 list_free(con->children);
158 list_free(cont->current.children); 124 list_free(con->current.children);
159 125 list_free(con->outputs);
160 switch (cont->type) {
161 case C_ROOT:
162 break;
163 case C_OUTPUT:
164 break;
165 case C_WORKSPACE:
166 container_workspace_free(cont->sway_workspace);
167 break;
168 case C_CONTAINER:
169 break;
170 case C_VIEW:
171 {
172 struct sway_view *view = cont->sway_view;
173 view->swayc = NULL;
174 free(view->title_format);
175 view->title_format = NULL;
176
177 if (view->destroying) {
178 view_free(view);
179 }
180 }
181 break;
182 case C_TYPES:
183 sway_assert(false, "Didn't expect to see C_TYPES here");
184 break;
185 }
186
187 free(cont);
188}
189
190static struct sway_container *container_destroy_noreaping(
191 struct sway_container *con);
192
193static struct sway_container *container_workspace_destroy(
194 struct sway_container *workspace) {
195 if (!sway_assert(workspace, "cannot destroy null workspace")) {
196 return NULL;
197 }
198
199 struct sway_container *output = container_parent(workspace, C_OUTPUT);
200
201 // If we're destroying the output, it will be NULL here. Return the root so
202 // that it doesn't appear that the workspace has refused to be destoyed,
203 // which would leave it in a broken state with no parent.
204 if (output == NULL) {
205 return &root_container;
206 }
207
208 // Do not destroy this if it's the last workspace on this output
209 if (output->children->length == 1) {
210 return NULL;
211 }
212
213 wlr_log(WLR_DEBUG, "destroying workspace '%s'", workspace->name);
214
215 if (!workspace_is_empty(workspace)) {
216 // Move children to a different workspace on this output
217 struct sway_container *new_workspace = NULL;
218 for (int i = 0; i < output->children->length; i++) {
219 if (output->children->items[i] != workspace) {
220 new_workspace = output->children->items[i];
221 break;
222 }
223 }
224
225 wlr_log(WLR_DEBUG, "moving children to different workspace '%s' -> '%s'",
226 workspace->name, new_workspace->name);
227 for (int i = 0; i < workspace->children->length; i++) {
228 container_move_to(workspace->children->items[i], new_workspace);
229 }
230 list_t *floating = workspace->sway_workspace->floating;
231 for (int i = 0; i < floating->length; i++) {
232 struct sway_container *floater = floating->items[i];
233 container_remove_child(floater);
234 workspace_add_floating(new_workspace, floater);
235 }
236 }
237
238 return output;
239}
240
241static struct sway_container *container_output_destroy(
242 struct sway_container *output) {
243 if (!sway_assert(output, "cannot destroy null output")) {
244 return NULL;
245 }
246
247 if (output->children->length > 0) {
248 // TODO save workspaces when there are no outputs.
249 // TODO also check if there will ever be no outputs except for exiting
250 // program
251 if (root_container.children->length > 1) {
252 // Move workspace from this output to another output
253 struct sway_container *fallback_output =
254 root_container.children->items[0];
255 if (fallback_output == output) {
256 fallback_output = root_container.children->items[1];
257 }
258
259 while (output->children->length) {
260 struct sway_container *workspace = output->children->items[0];
261
262 struct sway_container *new_output =
263 workspace_output_get_highest_available(workspace, output);
264 if (!new_output) {
265 new_output = fallback_output;
266 workspace_output_add_priority(workspace, new_output);
267 }
268 126
269 container_remove_child(workspace); 127 if (con->type == C_VIEW) {
270 if (!workspace_is_empty(workspace)) { 128 struct sway_view *view = con->sway_view;
271 container_add_child(new_output, workspace); 129 view->swayc = NULL;
272 ipc_event_workspace(NULL, workspace, "move"); 130 free(view->title_format);
273 } else { 131 view->title_format = NULL;
274 container_destroy(workspace);
275 }
276 132
277 output_sort_workspaces(new_output); 133 if (view->destroying) {
278 } 134 view_destroy(view);
279 } 135 }
280 } 136 }
281 137
282 wl_list_remove(&output->sway_output->mode.link); 138 free(con);
283 wl_list_remove(&output->sway_output->transform.link);
284 wl_list_remove(&output->sway_output->scale.link);
285
286 wl_list_remove(&output->sway_output->damage_destroy.link);
287 wl_list_remove(&output->sway_output->damage_frame.link);
288
289 output->sway_output->swayc = NULL;
290 output->sway_output = NULL;
291
292 wlr_log(WLR_DEBUG, "OUTPUT: Destroying output '%s'", output->name);
293
294 return &root_container;
295} 139}
296 140
297/** 141void container_begin_destroy(struct sway_container *con) {
298 * Implement the actual destroy logic, without reaping. 142 if (!sway_assert(con->type == C_CONTAINER || con->type == C_VIEW,
299 */ 143 "Expected a container or view")) {
300static struct sway_container *container_destroy_noreaping( 144 return;
301 struct sway_container *con) {
302 if (con == NULL) {
303 return NULL;
304 }
305 if (con->destroying) {
306 return NULL;
307 } 145 }
308 146
309 wl_signal_emit(&con->events.destroy, con);
310
311 // emit IPC event
312 if (con->type == C_VIEW) { 147 if (con->type == C_VIEW) {
313 ipc_event_window(con, "close"); 148 ipc_event_window(con, "close");
314 } else if (con->type == C_WORKSPACE) {
315 ipc_event_workspace(NULL, con, "empty");
316 }
317
318 // The below functions move their children to somewhere else.
319 if (con->type == C_OUTPUT) {
320 container_output_destroy(con);
321 } else if (con->type == C_WORKSPACE) {
322 // Workspaces will refuse to be destroyed if they're the last workspace
323 // on their output.
324 if (!container_workspace_destroy(con)) {
325 return NULL;
326 }
327 } 149 }
150 wl_signal_emit(&con->events.destroy, con);
328 151
329 container_end_mouse_operation(con); 152 container_end_mouse_operation(con);
330 153
@@ -335,51 +158,22 @@ static struct sway_container *container_destroy_noreaping(
335 root_scratchpad_remove_container(con); 158 root_scratchpad_remove_container(con);
336 } 159 }
337 160
338 if (!con->parent) { 161 if (con->parent) {
339 return NULL; 162 container_remove_child(con);
340 }
341
342 return container_remove_child(con);
343}
344
345bool container_reap_empty(struct sway_container *con) {
346 switch (con->type) {
347 case C_ROOT:
348 case C_OUTPUT:
349 // dont reap these
350 break;
351 case C_WORKSPACE:
352 if (!workspace_is_visible(con) && workspace_is_empty(con)) {
353 wlr_log(WLR_DEBUG, "Destroying workspace via reaper");
354 container_destroy_noreaping(con);
355 return true;
356 }
357 break;
358 case C_CONTAINER:
359 if (con->children->length == 0) {
360 container_destroy_noreaping(con);
361 return true;
362 }
363 case C_VIEW:
364 break;
365 case C_TYPES:
366 sway_assert(false, "container_reap_empty called on an invalid "
367 "container");
368 break;
369 } 163 }
370
371 return false;
372} 164}
373 165
374struct sway_container *container_reap_empty_recursive( 166struct sway_container *container_reap_empty(struct sway_container *con) {
375 struct sway_container *con) { 167 while (con && con->type == C_CONTAINER) {
376 while (con) {
377 struct sway_container *next = con->parent; 168 struct sway_container *next = con->parent;
378 if (!container_reap_empty(con)) { 169 if (con->children->length == 0) {
379 break; 170 container_begin_destroy(con);
380 } 171 }
381 con = next; 172 con = next;
382 } 173 }
174 if (con && con->type == C_WORKSPACE) {
175 workspace_consider_destroy(con);
176 }
383 return con; 177 return con;
384} 178}
385 179
@@ -388,34 +182,12 @@ struct sway_container *container_flatten(struct sway_container *container) {
388 struct sway_container *child = container->children->items[0]; 182 struct sway_container *child = container->children->items[0];
389 struct sway_container *parent = container->parent; 183 struct sway_container *parent = container->parent;
390 container_replace_child(container, child); 184 container_replace_child(container, child);
391 container_destroy_noreaping(container); 185 container_begin_destroy(container);
392 container = parent; 186 container = parent;
393 } 187 }
394 return container; 188 return container;
395} 189}
396 190
397/**
398 * container_destroy() is the first step in destroying a container. We'll emit
399 * events, detach it from the tree and mark it as destroying. The container will
400 * remain in memory until it's no longer used by a transaction, then it will be
401 * freed via container_free().
402 *
403 * This function just wraps container_destroy_noreaping(), then does reaping.
404 */
405struct sway_container *container_destroy(struct sway_container *con) {
406 if (con->is_fullscreen) {
407 struct sway_container *ws = container_parent(con, C_WORKSPACE);
408 ws->sway_workspace->fullscreen = NULL;
409 }
410 struct sway_container *parent = container_destroy_noreaping(con);
411
412 if (!parent) {
413 return NULL;
414 }
415
416 return container_reap_empty_recursive(parent);
417}
418
419static void container_close_func(struct sway_container *container, void *data) { 191static void container_close_func(struct sway_container *container, void *data) {
420 if (container->type == C_VIEW) { 192 if (container->type == C_VIEW) {
421 view_close(container->sway_view); 193 view_close(container->sway_view);
@@ -794,13 +566,24 @@ void container_damage_whole(struct sway_container *container) {
794 } 566 }
795} 567}
796 568
569/**
570 * Return the output which will be used for scale purposes.
571 * This is the most recently entered output.
572 */
573struct sway_output *container_get_effective_output(struct sway_container *con) {
574 if (con->outputs->length == 0) {
575 return NULL;
576 }
577 return con->outputs->items[con->outputs->length - 1];
578}
579
797static void update_title_texture(struct sway_container *con, 580static void update_title_texture(struct sway_container *con,
798 struct wlr_texture **texture, struct border_colors *class) { 581 struct wlr_texture **texture, struct border_colors *class) {
799 if (!sway_assert(con->type == C_CONTAINER || con->type == C_VIEW, 582 if (!sway_assert(con->type == C_CONTAINER || con->type == C_VIEW,
800 "Unexpected type %s", container_type_to_str(con->type))) { 583 "Unexpected type %s", container_type_to_str(con->type))) {
801 return; 584 return;
802 } 585 }
803 struct sway_container *output = container_parent(con, C_OUTPUT); 586 struct sway_output *output = container_get_effective_output(con);
804 if (!output) { 587 if (!output) {
805 return; 588 return;
806 } 589 }
@@ -812,7 +595,7 @@ static void update_title_texture(struct sway_container *con,
812 return; 595 return;
813 } 596 }
814 597
815 double scale = output->sway_output->wlr_output->scale; 598 double scale = output->wlr_output->scale;
816 int width = 0; 599 int width = 0;
817 int height = con->title_height * scale; 600 int height = con->title_height * scale;
818 601
@@ -840,7 +623,7 @@ static void update_title_texture(struct sway_container *con,
840 unsigned char *data = cairo_image_surface_get_data(surface); 623 unsigned char *data = cairo_image_surface_get_data(surface);
841 int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width); 624 int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width);
842 struct wlr_renderer *renderer = wlr_backend_get_renderer( 625 struct wlr_renderer *renderer = wlr_backend_get_renderer(
843 output->sway_output->wlr_output->backend); 626 output->wlr_output->backend);
844 *texture = wlr_texture_from_pixels( 627 *texture = wlr_texture_from_pixels(
845 renderer, WL_SHM_FORMAT_ARGB8888, stride, width, height, data); 628 renderer, WL_SHM_FORMAT_ARGB8888, stride, width, height, data);
846 cairo_surface_destroy(surface); 629 cairo_surface_destroy(surface);
@@ -1224,6 +1007,7 @@ void container_set_fullscreen(struct sway_container *container, bool enable) {
1224 container_set_fullscreen(workspace->sway_workspace->fullscreen, false); 1007 container_set_fullscreen(workspace->sway_workspace->fullscreen, false);
1225 } 1008 }
1226 1009
1010 set_fullscreen_iterator(container, &enable);
1227 container_for_each_child(container, set_fullscreen_iterator, &enable); 1011 container_for_each_child(container, set_fullscreen_iterator, &enable);
1228 1012
1229 container->is_fullscreen = enable; 1013 container->is_fullscreen = enable;
@@ -1289,3 +1073,323 @@ bool container_is_fullscreen_or_child(struct sway_container *container) {
1289 1073
1290 return false; 1074 return false;
1291} 1075}
1076
1077static void surface_send_enter_iterator(struct wlr_surface *surface,
1078 int x, int y, void *data) {
1079 struct wlr_output *wlr_output = data;
1080 wlr_surface_send_enter(surface, wlr_output);
1081}
1082
1083static void surface_send_leave_iterator(struct wlr_surface *surface,
1084 int x, int y, void *data) {
1085 struct wlr_output *wlr_output = data;
1086 wlr_surface_send_leave(surface, wlr_output);
1087}
1088
1089void container_discover_outputs(struct sway_container *con) {
1090 if (!sway_assert(con->type == C_CONTAINER || con->type == C_VIEW,
1091 "Expected a container or view")) {
1092 return;
1093 }
1094 struct wlr_box con_box = {
1095 .x = con->current.swayc_x,
1096 .y = con->current.swayc_y,
1097 .width = con->current.swayc_width,
1098 .height = con->current.swayc_height,
1099 };
1100 struct sway_output *old_output = container_get_effective_output(con);
1101
1102 for (int i = 0; i < root_container.children->length; ++i) {
1103 struct sway_container *output = root_container.children->items[i];
1104 struct sway_output *sway_output = output->sway_output;
1105 struct wlr_box output_box;
1106 container_get_box(output, &output_box);
1107 struct wlr_box intersection;
1108 bool intersects =
1109 wlr_box_intersection(&con_box, &output_box, &intersection);
1110 int index = list_find(con->outputs, sway_output);
1111
1112 if (intersects && index == -1) {
1113 // Send enter
1114 wlr_log(WLR_DEBUG, "Con %p entered output %p", con, sway_output);
1115 if (con->type == C_VIEW) {
1116 view_for_each_surface(con->sway_view,
1117 surface_send_enter_iterator, sway_output->wlr_output);
1118 }
1119 list_add(con->outputs, sway_output);
1120 } else if (!intersects && index != -1) {
1121 // Send leave
1122 wlr_log(WLR_DEBUG, "Con %p left output %p", con, sway_output);
1123 if (con->type == C_VIEW) {
1124 view_for_each_surface(con->sway_view,
1125 surface_send_leave_iterator, sway_output->wlr_output);
1126 }
1127 list_del(con->outputs, index);
1128 }
1129 }
1130 struct sway_output *new_output = container_get_effective_output(con);
1131 double old_scale = old_output ? old_output->wlr_output->scale : -1;
1132 double new_scale = new_output ? new_output->wlr_output->scale : -1;
1133 if (old_scale != new_scale) {
1134 container_update_title_textures(con);
1135 if (con->type == C_VIEW) {
1136 view_update_marks_textures(con->sway_view);
1137 }
1138 }
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 || c->type != C_VIEW) {
1163 return;
1164 }
1165
1166 c->current_gaps = c->has_gaps ? c->gaps_inner : config->gaps_inner;
1167 c->x += c->current_gaps;
1168 c->y += c->current_gaps;
1169 c->width -= 2 * c->current_gaps;
1170 c->height -= 2 * c->current_gaps;
1171}
1172
1173int container_sibling_index(const struct sway_container *child) {
1174 return list_find(child->parent->children, child);
1175}
1176
1177void container_handle_fullscreen_reparent(struct sway_container *con,
1178 struct sway_container *old_parent) {
1179 if (!con->is_fullscreen) {
1180 return;
1181 }
1182 struct sway_container *old_workspace = old_parent;
1183 if (old_workspace && old_workspace->type != C_WORKSPACE) {
1184 old_workspace = container_parent(old_workspace, C_WORKSPACE);
1185 }
1186 struct sway_container *new_workspace = container_parent(con, C_WORKSPACE);
1187 if (old_workspace == new_workspace) {
1188 return;
1189 }
1190 // Unmark the old workspace as fullscreen
1191 if (old_workspace) {
1192 old_workspace->sway_workspace->fullscreen = NULL;
1193 }
1194
1195 // Mark the new workspace as fullscreen
1196 if (new_workspace->sway_workspace->fullscreen) {
1197 container_set_fullscreen(
1198 new_workspace->sway_workspace->fullscreen, false);
1199 }
1200 new_workspace->sway_workspace->fullscreen = con;
1201
1202 // Resize container to new output dimensions
1203 struct sway_container *output = new_workspace->parent;
1204 con->x = output->x;
1205 con->y = output->y;
1206 con->width = output->width;
1207 con->height = output->height;
1208
1209 if (con->type == C_VIEW) {
1210 struct sway_view *view = con->sway_view;
1211 view->x = output->x;
1212 view->y = output->y;
1213 view->width = output->width;
1214 view->height = output->height;
1215 } else {
1216 arrange_windows(new_workspace);
1217 }
1218}
1219
1220void container_insert_child(struct sway_container *parent,
1221 struct sway_container *child, int i) {
1222 struct sway_container *old_parent = child->parent;
1223 if (old_parent) {
1224 container_remove_child(child);
1225 }
1226 wlr_log(WLR_DEBUG, "Inserting id:%zd at index %d", child->id, i);
1227 list_insert(parent->children, i, child);
1228 child->parent = parent;
1229 container_handle_fullscreen_reparent(child, old_parent);
1230}
1231
1232struct sway_container *container_add_sibling(struct sway_container *fixed,
1233 struct sway_container *active) {
1234 // TODO handle floating
1235 struct sway_container *old_parent = NULL;
1236 if (active->parent) {
1237 old_parent = active->parent;
1238 container_remove_child(active);
1239 }
1240 struct sway_container *parent = fixed->parent;
1241 int i = container_sibling_index(fixed);
1242 list_insert(parent->children, i + 1, active);
1243 active->parent = parent;
1244 container_handle_fullscreen_reparent(active, old_parent);
1245 return active->parent;
1246}
1247
1248void container_add_child(struct sway_container *parent,
1249 struct sway_container *child) {
1250 wlr_log(WLR_DEBUG, "Adding %p (%d, %fx%f) to %p (%d, %fx%f)",
1251 child, child->type, child->width, child->height,
1252 parent, parent->type, parent->width, parent->height);
1253 struct sway_container *old_parent = child->parent;
1254 list_add(parent->children, child);
1255 child->parent = parent;
1256 container_handle_fullscreen_reparent(child, old_parent);
1257 if (old_parent) {
1258 container_set_dirty(old_parent);
1259 }
1260 container_set_dirty(child);
1261}
1262
1263struct sway_container *container_remove_child(struct sway_container *child) {
1264 if (child->is_fullscreen) {
1265 struct sway_container *workspace = container_parent(child, C_WORKSPACE);
1266 workspace->sway_workspace->fullscreen = NULL;
1267 }
1268
1269 struct sway_container *parent = child->parent;
1270 list_t *list = container_is_floating(child) ?
1271 parent->sway_workspace->floating : parent->children;
1272 int index = list_find(list, child);
1273 if (index != -1) {
1274 list_del(list, index);
1275 }
1276 child->parent = NULL;
1277 container_notify_subtree_changed(parent);
1278
1279 container_set_dirty(parent);
1280 container_set_dirty(child);
1281
1282 return parent;
1283}
1284
1285enum sway_container_layout container_get_default_layout(
1286 struct sway_container *con) {
1287 if (con->type != C_OUTPUT) {
1288 con = container_parent(con, C_OUTPUT);
1289 }
1290
1291 if (!sway_assert(con != NULL,
1292 "container_get_default_layout must be called on an attached"
1293 " container below the root container")) {
1294 return 0;
1295 }
1296
1297 if (config->default_layout != L_NONE) {
1298 return config->default_layout;
1299 } else if (config->default_orientation != L_NONE) {
1300 return config->default_orientation;
1301 } else if (con->width >= con->height) {
1302 return L_HORIZ;
1303 } else {
1304 return L_VERT;
1305 }
1306}
1307
1308struct sway_container *container_replace_child(struct sway_container *child,
1309 struct sway_container *new_child) {
1310 struct sway_container *parent = child->parent;
1311 if (parent == NULL) {
1312 return NULL;
1313 }
1314
1315 list_t *list = container_is_floating(child) ?
1316 parent->sway_workspace->floating : parent->children;
1317 int i = list_find(list, child);
1318
1319 if (new_child->parent) {
1320 container_remove_child(new_child);
1321 }
1322 list->items[i] = new_child;
1323 new_child->parent = parent;
1324 child->parent = NULL;
1325
1326 // Set geometry for new child
1327 new_child->x = child->x;
1328 new_child->y = child->y;
1329 new_child->width = child->width;
1330 new_child->height = child->height;
1331
1332 // reset geometry for child
1333 child->width = 0;
1334 child->height = 0;
1335
1336 return parent;
1337}
1338
1339struct sway_container *container_split(struct sway_container *child,
1340 enum sway_container_layout layout) {
1341 // TODO floating: cannot split a floating container
1342 if (!sway_assert(child, "child cannot be null")) {
1343 return NULL;
1344 }
1345 if (child->type == C_WORKSPACE && child->children->length == 0) {
1346 // Special case: this just behaves like splitt
1347 child->prev_split_layout = child->layout;
1348 child->layout = layout;
1349 return child;
1350 }
1351
1352 struct sway_container *cont = container_create(C_CONTAINER);
1353
1354 wlr_log(WLR_DEBUG, "creating container %p around %p", cont, child);
1355
1356 child->type == C_WORKSPACE ? workspace_remove_gaps(child)
1357 : container_remove_gaps(child);
1358
1359 cont->prev_split_layout = L_NONE;
1360 cont->width = child->width;
1361 cont->height = child->height;
1362 cont->x = child->x;
1363 cont->y = child->y;
1364
1365 struct sway_seat *seat = input_manager_get_default_seat(input_manager);
1366 bool set_focus = (seat_get_focus(seat) == child);
1367
1368 container_add_gaps(cont);
1369
1370 if (child->type == C_WORKSPACE) {
1371 struct sway_container *workspace = child;
1372 while (workspace->children->length) {
1373 struct sway_container *ws_child = workspace->children->items[0];
1374 container_remove_child(ws_child);
1375 container_add_child(cont, ws_child);
1376 }
1377
1378 container_add_child(workspace, cont);
1379 enum sway_container_layout old_layout = workspace->layout;
1380 workspace->layout = layout;
1381 cont->layout = old_layout;
1382 } else {
1383 cont->layout = layout;
1384 container_replace_child(child, cont);
1385 container_add_child(cont, child);
1386 }
1387
1388 if (set_focus) {
1389 seat_set_focus(seat, cont);
1390 seat_set_focus(seat, child);
1391 }
1392
1393 container_notify_subtree_changed(cont);
1394 return cont;
1395}
diff --git a/sway/tree/layout.c b/sway/tree/layout.c
deleted file mode 100644
index 2f22a3dd..00000000
--- a/sway/tree/layout.c
+++ /dev/null
@@ -1,1027 +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 wl_signal_emit(&child->events.reparent, old_parent);
79}
80
81struct sway_container *container_add_sibling(struct sway_container *fixed,
82 struct sway_container *active) {
83 // TODO handle floating
84 struct sway_container *old_parent = NULL;
85 if (active->parent) {
86 old_parent = active->parent;
87 container_remove_child(active);
88 }
89 struct sway_container *parent = fixed->parent;
90 int i = index_child(fixed);
91 list_insert(parent->children, i + 1, active);
92 active->parent = parent;
93 container_handle_fullscreen_reparent(active, old_parent);
94 wl_signal_emit(&active->events.reparent, old_parent);
95 return active->parent;
96}
97
98void container_add_child(struct sway_container *parent,
99 struct sway_container *child) {
100 wlr_log(WLR_DEBUG, "Adding %p (%d, %fx%f) to %p (%d, %fx%f)",
101 child, child->type, child->width, child->height,
102 parent, parent->type, parent->width, parent->height);
103 struct sway_container *old_parent = child->parent;
104 list_add(parent->children, child);
105 child->parent = parent;
106 container_handle_fullscreen_reparent(child, old_parent);
107 if (old_parent) {
108 container_set_dirty(old_parent);
109 }
110 container_set_dirty(child);
111}
112
113struct sway_container *container_remove_child(struct sway_container *child) {
114 if (child->is_fullscreen) {
115 struct sway_container *workspace = container_parent(child, C_WORKSPACE);
116 workspace->sway_workspace->fullscreen = NULL;
117 }
118
119 struct sway_container *parent = child->parent;
120 list_t *list = container_is_floating(child) ?
121 parent->sway_workspace->floating : parent->children;
122 int index = list_find(list, child);
123 if (index != -1) {
124 list_del(list, index);
125 }
126 child->parent = NULL;
127 container_notify_subtree_changed(parent);
128
129 container_set_dirty(parent);
130 container_set_dirty(child);
131
132 return parent;
133}
134
135void container_move_to(struct sway_container *container,
136 struct sway_container *destination) {
137 if (!sway_assert(container->type == C_CONTAINER ||
138 container->type == C_VIEW, "Expected a container or view")) {
139 return;
140 }
141 if (container == destination
142 || container_has_ancestor(container, destination)) {
143 return;
144 }
145 struct sway_container *old_parent = NULL;
146 struct sway_container *new_parent = NULL;
147 if (container_is_floating(container)) {
148 // Resolve destination into a workspace
149 struct sway_container *new_ws = NULL;
150 if (destination->type == C_OUTPUT) {
151 new_ws = output_get_active_workspace(destination->sway_output);
152 } else if (destination->type == C_WORKSPACE) {
153 new_ws = destination;
154 } else {
155 new_ws = container_parent(destination, C_WORKSPACE);
156 }
157 if (!new_ws) {
158 // This can happen if the user has run "move container to mark foo",
159 // where mark foo is on a hidden scratchpad container.
160 return;
161 }
162 struct sway_container *old_output =
163 container_parent(container, C_OUTPUT);
164 old_parent = container_remove_child(container);
165 workspace_add_floating(new_ws, container);
166 container_handle_fullscreen_reparent(container, old_parent);
167 // If changing output, center it within the workspace
168 if (old_output != new_ws->parent && !container->is_fullscreen) {
169 container_floating_move_to_center(container);
170 }
171 } else {
172 old_parent = container_remove_child(container);
173 container->width = container->height = 0;
174 container->saved_width = container->saved_height = 0;
175
176 if (destination->type == C_VIEW) {
177 new_parent = container_add_sibling(destination, container);
178 } else {
179 new_parent = destination;
180 container_add_child(destination, container);
181 }
182 }
183
184 wl_signal_emit(&container->events.reparent, old_parent);
185
186 if (container->type == C_VIEW) {
187 ipc_event_window(container, "move");
188 }
189 container_notify_subtree_changed(old_parent);
190 container_notify_subtree_changed(new_parent);
191
192 // If view was moved to a fullscreen workspace, refocus the fullscreen view
193 struct sway_container *new_workspace = container;
194 if (new_workspace->type != C_WORKSPACE) {
195 new_workspace = container_parent(new_workspace, C_WORKSPACE);
196 }
197 if (new_workspace->sway_workspace->fullscreen) {
198 struct sway_seat *seat;
199 struct sway_container *focus, *focus_ws;
200 wl_list_for_each(seat, &input_manager->seats, link) {
201 focus = seat_get_focus(seat);
202 focus_ws = focus;
203 if (focus_ws->type != C_WORKSPACE) {
204 focus_ws = container_parent(focus_ws, C_WORKSPACE);
205 }
206 if (focus_ws == new_workspace) {
207 struct sway_container *new_focus = seat_get_focus_inactive(seat,
208 new_workspace->sway_workspace->fullscreen);
209 seat_set_focus(seat, new_focus);
210 }
211 }
212 }
213 // Update workspace urgent state
214 struct sway_container *old_workspace = old_parent;
215 if (old_workspace->type != C_WORKSPACE) {
216 old_workspace = container_parent(old_workspace, C_WORKSPACE);
217 }
218 if (new_workspace != old_workspace) {
219 workspace_detect_urgent(new_workspace);
220 if (old_workspace) {
221 workspace_detect_urgent(old_workspace);
222 }
223 }
224}
225
226static bool sway_dir_to_wlr(enum movement_direction dir,
227 enum wlr_direction *out) {
228 switch (dir) {
229 case MOVE_UP:
230 *out = WLR_DIRECTION_UP;
231 break;
232 case MOVE_DOWN:
233 *out = WLR_DIRECTION_DOWN;
234 break;
235 case MOVE_LEFT:
236 *out = WLR_DIRECTION_LEFT;
237 break;
238 case MOVE_RIGHT:
239 *out = WLR_DIRECTION_RIGHT;
240 break;
241 default:
242 return false;
243 }
244
245 return true;
246}
247
248static bool is_parallel(enum sway_container_layout layout,
249 enum movement_direction dir) {
250 switch (layout) {
251 case L_TABBED:
252 case L_HORIZ:
253 return dir == MOVE_LEFT || dir == MOVE_RIGHT;
254 case L_STACKED:
255 case L_VERT:
256 return dir == MOVE_UP || dir == MOVE_DOWN;
257 default:
258 return false;
259 }
260}
261
262static enum movement_direction invert_movement(enum movement_direction dir) {
263 switch (dir) {
264 case MOVE_LEFT:
265 return MOVE_RIGHT;
266 case MOVE_RIGHT:
267 return MOVE_LEFT;
268 case MOVE_UP:
269 return MOVE_DOWN;
270 case MOVE_DOWN:
271 return MOVE_UP;
272 default:
273 sway_assert(0, "This function expects left|right|up|down");
274 return MOVE_LEFT;
275 }
276}
277
278static int move_offs(enum movement_direction move_dir) {
279 return move_dir == MOVE_LEFT || move_dir == MOVE_UP ? -1 : 1;
280}
281
282/* Gets the index of the most extreme member based on the movement offset */
283static int container_limit(struct sway_container *container,
284 enum movement_direction move_dir) {
285 return move_offs(move_dir) < 0 ? 0 : container->children->length;
286}
287
288/* Takes one child, sets it aside, wraps the rest of the children in a new
289 * container, switches the layout of the workspace, and drops the child back in.
290 * In other words, rejigger it. */
291static void workspace_rejigger(struct sway_container *ws,
292 struct sway_container *child, enum movement_direction move_dir) {
293 struct sway_container *original_parent = child->parent;
294 struct sway_container *new_parent =
295 container_split(ws, ws->layout);
296
297 container_remove_child(child);
298 for (int i = 0; i < ws->children->length; ++i) {
299 struct sway_container *_child = ws->children->items[i];
300 container_move_to(new_parent, _child);
301 }
302
303 int index = move_offs(move_dir);
304 container_insert_child(ws, child, index < 0 ? 0 : 1);
305 ws->layout =
306 move_dir == MOVE_LEFT || move_dir == MOVE_RIGHT ? L_HORIZ : L_VERT;
307
308 container_flatten(ws);
309 container_reap_empty_recursive(original_parent);
310 wl_signal_emit(&child->events.reparent, original_parent);
311 container_create_notify(new_parent);
312}
313
314static void move_out_of_tabs_stacks(struct sway_container *container,
315 struct sway_container *current, enum movement_direction move_dir,
316 int offs) {
317 if (container->parent == current->parent
318 && current->parent->children->length == 1) {
319 wlr_log(WLR_DEBUG, "Changing layout of %zd", current->parent->id);
320 current->parent->layout = move_dir ==
321 MOVE_LEFT || move_dir == MOVE_RIGHT ? L_HORIZ : L_VERT;
322 return;
323 }
324
325 wlr_log(WLR_DEBUG, "Moving out of tab/stack into a split");
326 bool is_workspace = current->parent->type == C_WORKSPACE;
327 struct sway_container *new_parent = container_split(current->parent,
328 move_dir == MOVE_LEFT || move_dir == MOVE_RIGHT ? L_HORIZ : L_VERT);
329 if (is_workspace) {
330 container_insert_child(new_parent->parent, container, offs < 0 ? 0 : 1);
331 } else {
332 container_insert_child(new_parent, container, offs < 0 ? 0 : 1);
333 container_reap_empty_recursive(new_parent->parent);
334 container_flatten(new_parent->parent);
335 }
336 container_create_notify(new_parent);
337 container_notify_subtree_changed(new_parent);
338}
339
340void container_move(struct sway_container *container,
341 enum movement_direction move_dir, int move_amt) {
342 if (!sway_assert(
343 container->type != C_CONTAINER || container->type != C_VIEW,
344 "Can only move containers and views")) {
345 return;
346 }
347 int offs = move_offs(move_dir);
348
349 struct sway_container *sibling = NULL;
350 struct sway_container *current = container;
351 struct sway_container *parent = current->parent;
352 struct sway_container *top = &root_container;
353
354 // If moving a fullscreen view, only consider outputs
355 if (container->is_fullscreen) {
356 current = container_parent(container, C_OUTPUT);
357 } else if (container_is_fullscreen_or_child(container) ||
358 container_is_floating_or_child(container)) {
359 // If we've fullscreened a split container, only allow the child to move
360 // around within the fullscreen parent.
361 // Same with floating a split container.
362 struct sway_container *ws = container_parent(container, C_WORKSPACE);
363 top = ws->sway_workspace->fullscreen;
364 }
365
366 struct sway_container *new_parent = container_flatten(parent);
367 if (new_parent != parent) {
368 // Special case: we were the last one in this container, so leave
369 return;
370 }
371
372 while (!sibling) {
373 if (current == top) {
374 return;
375 }
376
377 parent = current->parent;
378 wlr_log(WLR_DEBUG, "Visiting %p %s '%s'", current,
379 container_type_to_str(current->type), current->name);
380
381 int index = index_child(current);
382
383 switch (current->type) {
384 case C_OUTPUT: {
385 enum wlr_direction wlr_dir = 0;
386 if (!sway_assert(sway_dir_to_wlr(move_dir, &wlr_dir),
387 "got invalid direction: %d", move_dir)) {
388 return;
389 }
390 double ref_lx = current->x + current->width / 2;
391 double ref_ly = current->y + current->height / 2;
392 struct wlr_output *next = wlr_output_layout_adjacent_output(
393 root_container.sway_root->output_layout, wlr_dir,
394 current->sway_output->wlr_output, ref_lx, ref_ly);
395 if (!next) {
396 wlr_log(WLR_DEBUG, "Hit edge of output, nowhere else to go");
397 return;
398 }
399 struct sway_output *next_output = next->data;
400 current = next_output->swayc;
401 wlr_log(WLR_DEBUG, "Selected next output (%s)", current->name);
402 // Select workspace and get outta here
403 current = seat_get_focus_inactive(
404 config->handler_context.seat, current);
405 if (current->type != C_WORKSPACE) {
406 current = container_parent(current, C_WORKSPACE);
407 }
408 sibling = current;
409 break;
410 }
411 case C_WORKSPACE:
412 if (!is_parallel(current->layout, move_dir)) {
413 if (current->children->length >= 2) {
414 wlr_log(WLR_DEBUG, "Rejiggering the workspace (%d kiddos)",
415 current->children->length);
416 workspace_rejigger(current, container, move_dir);
417 return;
418 } else {
419 wlr_log(WLR_DEBUG, "Selecting output");
420 current = current->parent;
421 }
422 } else if (current->layout == L_TABBED
423 || current->layout == L_STACKED) {
424 wlr_log(WLR_DEBUG, "Rejiggering out of tabs/stacks");
425 workspace_rejigger(current, container, move_dir);
426 } else {
427 wlr_log(WLR_DEBUG, "Selecting output");
428 current = current->parent;
429 }
430 break;
431 case C_CONTAINER:
432 case C_VIEW:
433 if (is_parallel(parent->layout, move_dir)) {
434 if ((index == parent->children->length - 1 && offs > 0)
435 || (index == 0 && offs < 0)) {
436 if (current->parent == container->parent) {
437 if (!parent->is_fullscreen &&
438 (parent->layout == L_TABBED ||
439 parent->layout == L_STACKED)) {
440 move_out_of_tabs_stacks(container, current,
441 move_dir, offs);
442 return;
443 } else {
444 wlr_log(WLR_DEBUG, "Hit limit, selecting parent");
445 current = current->parent;
446 }
447 } else {
448 wlr_log(WLR_DEBUG, "Hit limit, "
449 "promoting descendant to sibling");
450 // Special case
451 container_insert_child(current->parent, container,
452 index + (offs < 0 ? 0 : 1));
453 container->width = container->height = 0;
454 return;
455 }
456 } else {
457 sibling = parent->children->items[index + offs];
458 wlr_log(WLR_DEBUG, "Selecting sibling id:%zd", sibling->id);
459 }
460 } else if (!parent->is_fullscreen && (parent->layout == L_TABBED ||
461 parent->layout == L_STACKED)) {
462 move_out_of_tabs_stacks(container, current, move_dir, offs);
463 return;
464 } else {
465 wlr_log(WLR_DEBUG, "Moving up to find a parallel container");
466 current = current->parent;
467 }
468 break;
469 default:
470 sway_assert(0, "Not expecting to see container of type %s here",
471 container_type_to_str(current->type));
472 return;
473 }
474 }
475
476 // Part two: move stuff around
477 int index = index_child(container);
478 struct sway_container *old_parent = container->parent;
479
480 while (sibling) {
481 switch (sibling->type) {
482 case C_VIEW:
483 if (sibling->parent == container->parent) {
484 wlr_log(WLR_DEBUG, "Swapping siblings");
485 sibling->parent->children->items[index + offs] = container;
486 sibling->parent->children->items[index] = sibling;
487 } else {
488 wlr_log(WLR_DEBUG, "Promoting to sibling of cousin");
489 container_insert_child(sibling->parent, container,
490 index_child(sibling) + (offs > 0 ? 0 : 1));
491 container->width = container->height = 0;
492 }
493 sibling = NULL;
494 break;
495 case C_WORKSPACE: // Note: only in the case of moving between outputs
496 case C_CONTAINER:
497 if (is_parallel(sibling->layout, move_dir)) {
498 int limit = container_limit(sibling, invert_movement(move_dir));
499 wlr_log(WLR_DEBUG, "limit: %d", limit);
500 wlr_log(WLR_DEBUG,
501 "Reparenting container (parallel) to index %d "
502 "(move dir: %d)", limit, move_dir);
503 container_insert_child(sibling, container, limit);
504 container->width = container->height = 0;
505 sibling = NULL;
506 } else {
507 wlr_log(WLR_DEBUG, "Reparenting container (perpendicular)");
508 struct sway_container *focus_inactive = seat_get_focus_inactive(
509 config->handler_context.seat, sibling);
510 if (focus_inactive && focus_inactive != sibling) {
511 while (focus_inactive->parent != sibling) {
512 focus_inactive = focus_inactive->parent;
513 }
514 wlr_log(WLR_DEBUG, "Focus inactive: id:%zd",
515 focus_inactive->id);
516 sibling = focus_inactive;
517 continue;
518 } else if (sibling->children->length) {
519 wlr_log(WLR_DEBUG, "No focus-inactive, adding arbitrarily");
520 container_remove_child(container);
521 container_add_sibling(sibling->children->items[0], container);
522 } else {
523 wlr_log(WLR_DEBUG, "No kiddos, adding container alone");
524 container_remove_child(container);
525 container_add_child(sibling, container);
526 }
527 container->width = container->height = 0;
528 sibling = NULL;
529 }
530 break;
531 default:
532 sway_assert(0, "Not expecting to see container of type %s here",
533 container_type_to_str(sibling->type));
534 return;
535 }
536 }
537
538 container_notify_subtree_changed(old_parent);
539 container_notify_subtree_changed(container->parent);
540
541 if (container->type == C_VIEW) {
542 ipc_event_window(container, "move");
543 }
544
545 if (old_parent) {
546 seat_set_focus(config->handler_context.seat, old_parent);
547 seat_set_focus(config->handler_context.seat, container);
548 }
549
550 struct sway_container *last_ws = old_parent;
551 struct sway_container *next_ws = container->parent;
552 if (last_ws && last_ws->type != C_WORKSPACE) {
553 last_ws = container_parent(last_ws, C_WORKSPACE);
554 }
555 if (next_ws && next_ws->type != C_WORKSPACE) {
556 next_ws = container_parent(next_ws, C_WORKSPACE);
557 }
558 if (last_ws && next_ws && last_ws != next_ws) {
559 ipc_event_workspace(last_ws, next_ws, "focus");
560 workspace_detect_urgent(last_ws);
561 workspace_detect_urgent(next_ws);
562 }
563 container_end_mouse_operation(container);
564}
565
566enum sway_container_layout container_get_default_layout(
567 struct sway_container *con) {
568 if (con->type != C_OUTPUT) {
569 con = container_parent(con, C_OUTPUT);
570 }
571
572 if (!sway_assert(con != NULL,
573 "container_get_default_layout must be called on an attached"
574 " container below the root container")) {
575 return 0;
576 }
577
578 if (config->default_layout != L_NONE) {
579 return config->default_layout;
580 } else if (config->default_orientation != L_NONE) {
581 return config->default_orientation;
582 } else if (con->width >= con->height) {
583 return L_HORIZ;
584 } else {
585 return L_VERT;
586 }
587}
588
589/**
590 * Get swayc in the direction of newly entered output.
591 */
592static struct sway_container *get_swayc_in_output_direction(
593 struct sway_container *output, enum movement_direction dir,
594 struct sway_seat *seat) {
595 if (!output) {
596 return NULL;
597 }
598
599 struct sway_container *ws = seat_get_focus_inactive(seat, output);
600 if (ws->type != C_WORKSPACE) {
601 ws = container_parent(ws, C_WORKSPACE);
602 }
603
604 if (ws == NULL) {
605 wlr_log(WLR_ERROR, "got an output without a workspace");
606 return NULL;
607 }
608
609 if (ws->children->length > 0) {
610 switch (dir) {
611 case MOVE_LEFT:
612 if (ws->layout == L_HORIZ || ws->layout == L_TABBED) {
613 // get most right child of new output
614 return ws->children->items[ws->children->length-1];
615 } else {
616 return seat_get_focus_inactive(seat, ws);
617 }
618 case MOVE_RIGHT:
619 if (ws->layout == L_HORIZ || ws->layout == L_TABBED) {
620 // get most left child of new output
621 return ws->children->items[0];
622 } else {
623 return seat_get_focus_inactive(seat, ws);
624 }
625 case MOVE_UP:
626 case MOVE_DOWN: {
627 struct sway_container *focused =
628 seat_get_focus_inactive(seat, ws);
629 if (focused && focused->parent) {
630 struct sway_container *parent = focused->parent;
631 if (parent->layout == L_VERT) {
632 if (dir == MOVE_UP) {
633 // get child furthest down on new output
634 int idx = parent->children->length - 1;
635 return parent->children->items[idx];
636 } else if (dir == MOVE_DOWN) {
637 // get child furthest up on new output
638 return parent->children->items[0];
639 }
640 }
641 return focused;
642 }
643 break;
644 }
645 default:
646 break;
647 }
648 }
649
650 return ws;
651}
652
653static struct sway_container *sway_output_from_wlr(struct wlr_output *output) {
654 if (output == NULL) {
655 return NULL;
656 }
657 for (int i = 0; i < root_container.children->length; ++i) {
658 struct sway_container *o = root_container.children->items[i];
659 if (o->type == C_OUTPUT && o->sway_output->wlr_output == output) {
660 return o;
661 }
662 }
663 return NULL;
664}
665
666struct sway_container *container_get_in_direction(
667 struct sway_container *container, struct sway_seat *seat,
668 enum movement_direction dir) {
669 struct sway_container *parent = container->parent;
670
671 if (dir == MOVE_CHILD) {
672 return seat_get_focus_inactive(seat, container);
673 }
674 if (container->is_fullscreen) {
675 if (dir == MOVE_PARENT) {
676 return NULL;
677 }
678 container = container_parent(container, C_OUTPUT);
679 parent = container->parent;
680 } else {
681 if (dir == MOVE_PARENT) {
682 if (parent->type == C_OUTPUT || container_is_floating(container)) {
683 return NULL;
684 } else {
685 return parent;
686 }
687 }
688 }
689
690 struct sway_container *wrap_candidate = NULL;
691 while (true) {
692 bool can_move = false;
693 int desired;
694 int idx = index_child(container);
695 if (idx == -1) {
696 return NULL;
697 }
698 if (parent->type == C_ROOT) {
699 enum wlr_direction wlr_dir = 0;
700 if (!sway_assert(sway_dir_to_wlr(dir, &wlr_dir),
701 "got invalid direction: %d", dir)) {
702 return NULL;
703 }
704 int lx = container->x + container->width / 2;
705 int ly = container->y + container->height / 2;
706 struct wlr_output_layout *layout =
707 root_container.sway_root->output_layout;
708 struct wlr_output *wlr_adjacent =
709 wlr_output_layout_adjacent_output(layout, wlr_dir,
710 container->sway_output->wlr_output, lx, ly);
711 struct sway_container *adjacent =
712 sway_output_from_wlr(wlr_adjacent);
713
714 if (!adjacent || adjacent == container) {
715 if (!wrap_candidate) {
716 return NULL;
717 }
718 return seat_get_focus_inactive_view(seat, wrap_candidate);
719 }
720 struct sway_container *next =
721 get_swayc_in_output_direction(adjacent, dir, seat);
722 if (next == NULL) {
723 return NULL;
724 }
725 struct sway_container *next_workspace = next;
726 if (next_workspace->type != C_WORKSPACE) {
727 next_workspace = container_parent(next_workspace, C_WORKSPACE);
728 }
729 sway_assert(next_workspace, "Next container has no workspace");
730 if (next_workspace->sway_workspace->fullscreen) {
731 return seat_get_focus_inactive(seat,
732 next_workspace->sway_workspace->fullscreen);
733 }
734 if (next->children && next->children->length) {
735 // TODO consider floating children as well
736 return seat_get_focus_inactive_view(seat, next);
737 } else {
738 return next;
739 }
740 } else {
741 if (dir == MOVE_LEFT || dir == MOVE_RIGHT) {
742 if (parent->layout == L_HORIZ || parent->layout == L_TABBED) {
743 can_move = true;
744 desired = idx + (dir == MOVE_LEFT ? -1 : 1);
745 }
746 } else {
747 if (parent->layout == L_VERT || parent->layout == L_STACKED) {
748 can_move = true;
749 desired = idx + (dir == MOVE_UP ? -1 : 1);
750 }
751 }
752 }
753
754 if (can_move) {
755 // TODO handle floating
756 if (desired < 0 || desired >= parent->children->length) {
757 can_move = false;
758 int len = parent->children->length;
759 if (config->focus_wrapping != WRAP_NO && !wrap_candidate
760 && len > 1) {
761 if (desired < 0) {
762 wrap_candidate = parent->children->items[len-1];
763 } else {
764 wrap_candidate = parent->children->items[0];
765 }
766 if (config->focus_wrapping == WRAP_FORCE) {
767 return seat_get_focus_inactive_view(seat,
768 wrap_candidate);
769 }
770 }
771 } else {
772 struct sway_container *desired_con =
773 parent->children->items[desired];
774 wlr_log(WLR_DEBUG,
775 "cont %d-%p dir %i sibling %d: %p", idx,
776 container, dir, desired, desired_con);
777 return seat_get_focus_inactive_view(seat, desired_con);
778 }
779 }
780
781 if (!can_move) {
782 container = parent;
783 parent = parent->parent;
784 if (!parent) {
785 // wrapping is the last chance
786 if (!wrap_candidate) {
787 return NULL;
788 }
789 return seat_get_focus_inactive_view(seat, wrap_candidate);
790 }
791 }
792 }
793}
794
795struct sway_container *container_replace_child(struct sway_container *child,
796 struct sway_container *new_child) {
797 struct sway_container *parent = child->parent;
798 if (parent == NULL) {
799 return NULL;
800 }
801
802 list_t *list = container_is_floating(child) ?
803 parent->sway_workspace->floating : parent->children;
804 int i = list_find(list, child);
805
806 if (new_child->parent) {
807 container_remove_child(new_child);
808 }
809 list->items[i] = new_child;
810 new_child->parent = parent;
811 child->parent = NULL;
812
813 // Set geometry for new child
814 new_child->x = child->x;
815 new_child->y = child->y;
816 new_child->width = child->width;
817 new_child->height = child->height;
818
819 // reset geometry for child
820 child->width = 0;
821 child->height = 0;
822
823 return parent;
824}
825
826struct sway_container *container_split(struct sway_container *child,
827 enum sway_container_layout layout) {
828 // TODO floating: cannot split a floating container
829 if (!sway_assert(child, "child cannot be null")) {
830 return NULL;
831 }
832 if (child->type == C_WORKSPACE && child->children->length == 0) {
833 // Special case: this just behaves like splitt
834 child->prev_split_layout = child->layout;
835 child->layout = layout;
836 return child;
837 }
838
839 struct sway_container *cont = container_create(C_CONTAINER);
840
841 wlr_log(WLR_DEBUG, "creating container %p around %p", cont, child);
842
843 remove_gaps(child);
844
845 cont->prev_split_layout = L_NONE;
846 cont->width = child->width;
847 cont->height = child->height;
848 cont->x = child->x;
849 cont->y = child->y;
850
851 struct sway_seat *seat = input_manager_get_default_seat(input_manager);
852 bool set_focus = (seat_get_focus(seat) == child);
853
854 add_gaps(cont);
855
856 if (child->type == C_WORKSPACE) {
857 struct sway_container *workspace = child;
858 while (workspace->children->length) {
859 struct sway_container *ws_child = workspace->children->items[0];
860 container_remove_child(ws_child);
861 container_add_child(cont, ws_child);
862 wl_signal_emit(&ws_child->events.reparent, workspace);
863 }
864
865 container_add_child(workspace, cont);
866 enum sway_container_layout old_layout = workspace->layout;
867 workspace->layout = layout;
868 cont->layout = old_layout;
869 } else {
870 struct sway_container *old_parent = child->parent;
871 cont->layout = layout;
872 container_replace_child(child, cont);
873 container_add_child(cont, child);
874 wl_signal_emit(&child->events.reparent, old_parent);
875 }
876
877 if (set_focus) {
878 seat_set_focus(seat, cont);
879 seat_set_focus(seat, child);
880 }
881
882 container_notify_subtree_changed(cont);
883 return cont;
884}
885
886void container_recursive_resize(struct sway_container *container,
887 double amount, enum resize_edge edge) {
888 bool layout_match = true;
889 wlr_log(WLR_DEBUG, "Resizing %p with amount: %f", container, amount);
890 if (edge == RESIZE_EDGE_LEFT || edge == RESIZE_EDGE_RIGHT) {
891 container->width += amount;
892 layout_match = container->layout == L_HORIZ;
893 } else if (edge == RESIZE_EDGE_TOP || edge == RESIZE_EDGE_BOTTOM) {
894 container->height += amount;
895 layout_match = container->layout == L_VERT;
896 }
897 if (container->children) {
898 for (int i = 0; i < container->children->length; i++) {
899 struct sway_container *child = container->children->items[i];
900 double amt = layout_match ?
901 amount / container->children->length : amount;
902 container_recursive_resize(child, amt, edge);
903 }
904 }
905}
906
907static void swap_places(struct sway_container *con1,
908 struct sway_container *con2) {
909 struct sway_container *temp = malloc(sizeof(struct sway_container));
910 temp->x = con1->x;
911 temp->y = con1->y;
912 temp->width = con1->width;
913 temp->height = con1->height;
914 temp->parent = con1->parent;
915
916 con1->x = con2->x;
917 con1->y = con2->y;
918 con1->width = con2->width;
919 con1->height = con2->height;
920
921 con2->x = temp->x;
922 con2->y = temp->y;
923 con2->width = temp->width;
924 con2->height = temp->height;
925
926 int temp_index = index_child(con1);
927 container_insert_child(con2->parent, con1, index_child(con2));
928 container_insert_child(temp->parent, con2, temp_index);
929
930 free(temp);
931}
932
933static void swap_focus(struct sway_container *con1,
934 struct sway_container *con2, struct sway_seat *seat,
935 struct sway_container *focus) {
936 if (focus == con1 || focus == con2) {
937 struct sway_container *ws1 = container_parent(con1, C_WORKSPACE);
938 struct sway_container *ws2 = container_parent(con2, C_WORKSPACE);
939 if (focus == con1 && (con2->parent->layout == L_TABBED
940 || con2->parent->layout == L_STACKED)) {
941 if (workspace_is_visible(ws2)) {
942 seat_set_focus_warp(seat, con2, false, true);
943 }
944 seat_set_focus(seat, ws1 != ws2 ? con2 : con1);
945 } else if (focus == con2 && (con1->parent->layout == L_TABBED
946 || con1->parent->layout == L_STACKED)) {
947 if (workspace_is_visible(ws1)) {
948 seat_set_focus_warp(seat, con1, false, true);
949 }
950 seat_set_focus(seat, ws1 != ws2 ? con1 : con2);
951 } else if (ws1 != ws2) {
952 seat_set_focus(seat, focus == con1 ? con2 : con1);
953 } else {
954 seat_set_focus(seat, focus);
955 }
956 } else {
957 seat_set_focus(seat, focus);
958 }
959}
960
961void container_swap(struct sway_container *con1, struct sway_container *con2) {
962 if (!sway_assert(con1 && con2, "Cannot swap with nothing")) {
963 return;
964 }
965 if (!sway_assert(con1->type >= C_CONTAINER && con2->type >= C_CONTAINER,
966 "Can only swap containers and views")) {
967 return;
968 }
969 if (!sway_assert(!container_has_ancestor(con1, con2)
970 && !container_has_ancestor(con2, con1),
971 "Cannot swap ancestor and descendant")) {
972 return;
973 }
974 if (!sway_assert(!container_is_floating(con1)
975 && !container_is_floating(con2),
976 "Swapping with floating containers is not supported")) {
977 return;
978 }
979
980 wlr_log(WLR_DEBUG, "Swapping containers %zu and %zu", con1->id, con2->id);
981
982 int fs1 = con1->is_fullscreen;
983 int fs2 = con2->is_fullscreen;
984 if (fs1) {
985 container_set_fullscreen(con1, false);
986 }
987 if (fs2) {
988 container_set_fullscreen(con2, false);
989 }
990
991 struct sway_seat *seat = input_manager_get_default_seat(input_manager);
992 struct sway_container *focus = seat_get_focus(seat);
993 struct sway_container *vis1 = container_parent(
994 seat_get_focus_inactive(seat, container_parent(con1, C_OUTPUT)),
995 C_WORKSPACE);
996 struct sway_container *vis2 = container_parent(
997 seat_get_focus_inactive(seat, container_parent(con2, C_OUTPUT)),
998 C_WORKSPACE);
999
1000 char *stored_prev_name = NULL;
1001 if (prev_workspace_name) {
1002 stored_prev_name = strdup(prev_workspace_name);
1003 }
1004
1005 swap_places(con1, con2);
1006
1007 if (!workspace_is_visible(vis1)) {
1008 seat_set_focus(seat, seat_get_focus_inactive(seat, vis1));
1009 }
1010 if (!workspace_is_visible(vis2)) {
1011 seat_set_focus(seat, seat_get_focus_inactive(seat, vis2));
1012 }
1013
1014 swap_focus(con1, con2, seat, focus);
1015
1016 if (stored_prev_name) {
1017 free(prev_workspace_name);
1018 prev_workspace_name = stored_prev_name;
1019 }
1020
1021 if (fs1) {
1022 container_set_fullscreen(con2, true);
1023 }
1024 if (fs2) {
1025 container_set_fullscreen(con1, true);
1026 }
1027}
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..2dd8f9f2 100644
--- a/sway/tree/root.c
+++ b/sway/tree/root.c
@@ -32,13 +32,14 @@ void root_create(void) {
32 32
33 root_container.sway_root = calloc(1, sizeof(*root_container.sway_root)); 33 root_container.sway_root = calloc(1, sizeof(*root_container.sway_root));
34 root_container.sway_root->output_layout = wlr_output_layout_create(); 34 root_container.sway_root->output_layout = wlr_output_layout_create();
35 wl_list_init(&root_container.sway_root->outputs); 35 wl_list_init(&root_container.sway_root->all_outputs);
36#ifdef HAVE_XWAYLAND 36#ifdef HAVE_XWAYLAND
37 wl_list_init(&root_container.sway_root->xwayland_unmanaged); 37 wl_list_init(&root_container.sway_root->xwayland_unmanaged);
38#endif 38#endif
39 wl_list_init(&root_container.sway_root->drag_icons); 39 wl_list_init(&root_container.sway_root->drag_icons);
40 wl_signal_init(&root_container.sway_root->events.new_container); 40 wl_signal_init(&root_container.sway_root->events.new_container);
41 root_container.sway_root->scratchpad = create_list(); 41 root_container.sway_root->scratchpad = create_list();
42 root_container.sway_root->saved_workspaces = create_list();
42 43
43 root_container.sway_root->output_layout_change.notify = 44 root_container.sway_root->output_layout_change.notify =
44 output_layout_handle_change; 45 output_layout_handle_change;
@@ -50,6 +51,7 @@ void root_destroy(void) {
50 // sway_root 51 // sway_root
51 wl_list_remove(&root_container.sway_root->output_layout_change.link); 52 wl_list_remove(&root_container.sway_root->output_layout_change.link);
52 list_free(root_container.sway_root->scratchpad); 53 list_free(root_container.sway_root->scratchpad);
54 list_free(root_container.sway_root->saved_workspaces);
53 wlr_output_layout_destroy(root_container.sway_root->output_layout); 55 wlr_output_layout_destroy(root_container.sway_root->output_layout);
54 free(root_container.sway_root); 56 free(root_container.sway_root);
55 57
diff --git a/sway/tree/view.c b/sway/tree/view.c
index b77a9bb2..2870d4f5 100644
--- a/sway/tree/view.c
+++ b/sway/tree/view.c
@@ -18,7 +18,6 @@
18#include "sway/input/seat.h" 18#include "sway/input/seat.h"
19#include "sway/tree/arrange.h" 19#include "sway/tree/arrange.h"
20#include "sway/tree/container.h" 20#include "sway/tree/container.h"
21#include "sway/tree/layout.h"
22#include "sway/tree/view.h" 21#include "sway/tree/view.h"
23#include "sway/tree/workspace.h" 22#include "sway/tree/workspace.h"
24#include "sway/config.h" 23#include "sway/config.h"
@@ -35,7 +34,7 @@ void view_init(struct sway_view *view, enum sway_view_type type,
35 wl_signal_init(&view->events.unmap); 34 wl_signal_init(&view->events.unmap);
36} 35}
37 36
38void view_free(struct sway_view *view) { 37void view_destroy(struct sway_view *view) {
39 if (!sway_assert(view->surface == NULL, "Tried to free mapped view")) { 38 if (!sway_assert(view->surface == NULL, "Tried to free mapped view")) {
40 return; 39 return;
41 } 40 }
@@ -75,14 +74,14 @@ void view_free(struct sway_view *view) {
75 * destroying flag will make the view get freed when the transaction is 74 * destroying flag will make the view get freed when the transaction is
76 * finished. 75 * finished.
77 */ 76 */
78void view_destroy(struct sway_view *view) { 77void view_begin_destroy(struct sway_view *view) {
79 if (!sway_assert(view->surface == NULL, "Tried to destroy a mapped view")) { 78 if (!sway_assert(view->surface == NULL, "Tried to destroy a mapped view")) {
80 return; 79 return;
81 } 80 }
82 view->destroying = true; 81 view->destroying = true;
83 82
84 if (!view->swayc) { 83 if (!view->swayc) {
85 view_free(view); 84 view_destroy(view);
86 } 85 }
87} 86}
88 87
@@ -236,7 +235,12 @@ void view_autoconfigure(struct sway_view *view) {
236 view->border_top = false; 235 view->border_top = false;
237 } 236 }
238 237
239 switch (view->border) { 238 enum sway_container_border border = view->border;
239 if (view->using_csd) {
240 border = B_NONE;
241 }
242
243 switch (border) {
240 case B_NONE: 244 case B_NONE:
241 x = con->x; 245 x = con->x;
242 y = con->y + y_offset; 246 y = con->y + y_offset;
@@ -364,48 +368,6 @@ static void view_handle_surface_new_subsurface(struct wl_listener *listener,
364 view_subsurface_create(view, subsurface); 368 view_subsurface_create(view, subsurface);
365} 369}
366 370
367static void surface_send_enter_iterator(struct wlr_surface *surface,
368 int x, int y, void *data) {
369 struct wlr_output *wlr_output = data;
370 wlr_surface_send_enter(surface, wlr_output);
371}
372
373static void surface_send_leave_iterator(struct wlr_surface *surface,
374 int x, int y, void *data) {
375 struct wlr_output *wlr_output = data;
376 wlr_surface_send_leave(surface, wlr_output);
377}
378
379static void view_handle_container_reparent(struct wl_listener *listener,
380 void *data) {
381 struct sway_view *view =
382 wl_container_of(listener, view, container_reparent);
383 struct sway_container *old_parent = data;
384
385 struct sway_container *old_output = old_parent;
386 if (old_output != NULL && old_output->type != C_OUTPUT) {
387 old_output = container_parent(old_output, C_OUTPUT);
388 }
389
390 struct sway_container *new_output = view->swayc->parent;
391 if (new_output != NULL && new_output->type != C_OUTPUT) {
392 new_output = container_parent(new_output, C_OUTPUT);
393 }
394
395 if (old_output == new_output) {
396 return;
397 }
398
399 if (old_output != NULL) {
400 view_for_each_surface(view, surface_send_leave_iterator,
401 old_output->sway_output->wlr_output);
402 }
403 if (new_output != NULL) {
404 view_for_each_surface(view, surface_send_enter_iterator,
405 new_output->sway_output->wlr_output);
406 }
407}
408
409static bool view_has_executed_criteria(struct sway_view *view, 371static bool view_has_executed_criteria(struct sway_view *view,
410 struct criteria *criteria) { 372 struct criteria *criteria) {
411 for (int i = 0; i < view->executed_criteria->length; ++i) { 373 for (int i = 0; i < view->executed_criteria->length; ++i) {
@@ -567,9 +529,6 @@ void view_map(struct sway_view *view, struct wlr_surface *wlr_surface) {
567 &view->surface_new_subsurface); 529 &view->surface_new_subsurface);
568 view->surface_new_subsurface.notify = view_handle_surface_new_subsurface; 530 view->surface_new_subsurface.notify = view_handle_surface_new_subsurface;
569 531
570 wl_signal_add(&view->swayc->events.reparent, &view->container_reparent);
571 view->container_reparent.notify = view_handle_container_reparent;
572
573 if (view->impl->wants_floating && view->impl->wants_floating(view)) { 532 if (view->impl->wants_floating && view->impl->wants_floating(view)) {
574 view->border = config->floating_border; 533 view->border = config->floating_border;
575 view->border_thickness = config->floating_border_thickness; 534 view->border_thickness = config->floating_border_thickness;
@@ -587,15 +546,12 @@ void view_map(struct sway_view *view, struct wlr_surface *wlr_surface) {
587 view_update_title(view, false); 546 view_update_title(view, false);
588 container_notify_subtree_changed(view->swayc->parent); 547 container_notify_subtree_changed(view->swayc->parent);
589 view_execute_criteria(view); 548 view_execute_criteria(view);
590
591 view_handle_container_reparent(&view->container_reparent, NULL);
592} 549}
593 550
594void view_unmap(struct sway_view *view) { 551void view_unmap(struct sway_view *view) {
595 wl_signal_emit(&view->events.unmap, view); 552 wl_signal_emit(&view->events.unmap, view);
596 553
597 wl_list_remove(&view->surface_new_subsurface.link); 554 wl_list_remove(&view->surface_new_subsurface.link);
598 wl_list_remove(&view->container_reparent.link);
599 555
600 if (view->urgent_timer) { 556 if (view->urgent_timer) {
601 wl_event_source_remove(view->urgent_timer); 557 wl_event_source_remove(view->urgent_timer);
@@ -603,7 +559,9 @@ void view_unmap(struct sway_view *view) {
603 } 559 }
604 560
605 bool was_fullscreen = view->swayc->is_fullscreen; 561 bool was_fullscreen = view->swayc->is_fullscreen;
606 struct sway_container *surviving_ancestor = container_destroy(view->swayc); 562 struct sway_container *parent = view->swayc->parent;
563 container_begin_destroy(view->swayc);
564 struct sway_container *surviving_ancestor = container_reap_empty(parent);
607 565
608 // If the workspace wasn't reaped 566 // If the workspace wasn't reaped
609 if (surviving_ancestor && surviving_ancestor->type >= C_WORKSPACE) { 567 if (surviving_ancestor && surviving_ancestor->type >= C_WORKSPACE) {
@@ -937,7 +895,7 @@ void view_add_mark(struct sway_view *view, char *mark) {
937 895
938static void update_marks_texture(struct sway_view *view, 896static void update_marks_texture(struct sway_view *view,
939 struct wlr_texture **texture, struct border_colors *class) { 897 struct wlr_texture **texture, struct border_colors *class) {
940 struct sway_container *output = container_parent(view->swayc, C_OUTPUT); 898 struct sway_output *output = container_get_effective_output(view->swayc);
941 if (!output) { 899 if (!output) {
942 return; 900 return;
943 } 901 }
@@ -973,7 +931,7 @@ static void update_marks_texture(struct sway_view *view,
973 } 931 }
974 free(part); 932 free(part);
975 933
976 double scale = output->sway_output->wlr_output->scale; 934 double scale = output->wlr_output->scale;
977 int width = 0; 935 int width = 0;
978 int height = view->swayc->title_height * scale; 936 int height = view->swayc->title_height * scale;
979 937
@@ -999,7 +957,7 @@ static void update_marks_texture(struct sway_view *view,
999 unsigned char *data = cairo_image_surface_get_data(surface); 957 unsigned char *data = cairo_image_surface_get_data(surface);
1000 int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width); 958 int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width);
1001 struct wlr_renderer *renderer = wlr_backend_get_renderer( 959 struct wlr_renderer *renderer = wlr_backend_get_renderer(
1002 output->sway_output->wlr_output->backend); 960 output->wlr_output->backend);
1003 *texture = wlr_texture_from_pixels( 961 *texture = wlr_texture_from_pixels(
1004 renderer, WL_SHM_FORMAT_ARGB8888, stride, width, height, data); 962 renderer, WL_SHM_FORMAT_ARGB8888, stride, width, height, data);
1005 cairo_surface_destroy(surface); 963 cairo_surface_destroy(surface);
diff --git a/sway/tree/workspace.c b/sway/tree/workspace.c
index 292f2c9a..d930826e 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) {
@@ -205,6 +264,7 @@ char *workspace_next_name(const char *output_name) {
205 && workspace_by_name(wso->workspace) == NULL) { 264 && workspace_by_name(wso->workspace) == NULL) {
206 free(target); 265 free(target);
207 target = strdup(wso->workspace); 266 target = strdup(wso->workspace);
267 break;
208 } 268 }
209 } 269 }
210 if (target != NULL) { 270 if (target != NULL) {
@@ -420,9 +480,7 @@ bool workspace_switch(struct sway_container *workspace,
420 // 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
421 // workspace here. 481 // workspace here.
422 ipc_event_workspace(active_ws, workspace, "focus"); 482 ipc_event_workspace(active_ws, workspace, "focus");
423 if (!workspace_is_visible(active_ws) && workspace_is_empty(active_ws)) { 483 workspace_consider_destroy(active_ws);
424 container_destroy(active_ws);
425 }
426 } 484 }
427 seat_set_focus(seat, next); 485 seat_set_focus(seat, next);
428 struct sway_container *output = container_parent(workspace, C_OUTPUT); 486 struct sway_container *output = container_parent(workspace, C_OUTPUT);
@@ -607,3 +665,38 @@ void workspace_add_floating(struct sway_container *workspace,
607 container_set_dirty(workspace); 665 container_set_dirty(workspace);
608 container_set_dirty(con); 666 container_set_dirty(con);
609} 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_inner : config->gaps_inner;
698 ws->x += ws->current_gaps;
699 ws->y += ws->current_gaps;
700 ws->width -= 2 * ws->current_gaps;
701 ws->height -= 2 * ws->current_gaps;
702}
diff --git a/swaynag/config.c b/swaynag/config.c
index d6c5739d..4d0824c9 100644
--- a/swaynag/config.c
+++ b/swaynag/config.c
@@ -180,8 +180,8 @@ int swaynag_parse_options(int argc, char **argv, struct swaynag *swaynag,
180 break; 180 break;
181 case 'L': // Detailed Button Text 181 case 'L': // Detailed Button Text
182 if (swaynag) { 182 if (swaynag) {
183 free(swaynag->details.button_details.text); 183 free(swaynag->details.button_details->text);
184 swaynag->details.button_details.text = strdup(optarg); 184 swaynag->details.button_details->text = strdup(optarg);
185 } 185 }
186 break; 186 break;
187 case 'm': // Message 187 case 'm': // Message
diff --git a/swaynag/main.c b/swaynag/main.c
index 6b0b5236..d1a0d236 100644
--- a/swaynag/main.c
+++ b/swaynag/main.c
@@ -34,9 +34,10 @@ int main(int argc, char **argv) {
34 button_close->type = SWAYNAG_ACTION_DISMISS; 34 button_close->type = SWAYNAG_ACTION_DISMISS;
35 list_add(swaynag.buttons, button_close); 35 list_add(swaynag.buttons, button_close);
36 36
37 swaynag.details.button_details.text = strdup("Toggle Details"); 37 swaynag.details.button_details =
38 swaynag.details.button_details.type = SWAYNAG_ACTION_EXPAND; 38 calloc(sizeof(struct swaynag_button), 1);
39 39 swaynag.details.button_details->text = strdup("Toggle Details");
40 swaynag.details.button_details->type = SWAYNAG_ACTION_EXPAND;
40 41
41 char *config_path = NULL; 42 char *config_path = NULL;
42 bool debug = false; 43 bool debug = false;
@@ -99,9 +100,10 @@ int main(int argc, char **argv) {
99 swaynag_types_free(types); 100 swaynag_types_free(types);
100 101
101 if (swaynag.details.message) { 102 if (swaynag.details.message) {
102 list_add(swaynag.buttons, &swaynag.details.button_details); 103 list_add(swaynag.buttons, swaynag.details.button_details);
103 } else { 104 } else {
104 free(swaynag.details.button_details.text); 105 free(swaynag.details.button_details->text);
106 free(swaynag.details.button_details);
105 } 107 }
106 108
107 wlr_log(WLR_DEBUG, "Output: %s", swaynag.type->output); 109 wlr_log(WLR_DEBUG, "Output: %s", swaynag.type->output);
@@ -123,7 +125,8 @@ int main(int argc, char **argv) {
123 125
124cleanup: 126cleanup:
125 swaynag_types_free(types); 127 swaynag_types_free(types);
126 free(swaynag.details.button_details.text); 128 free(swaynag.details.button_details->text);
129 free(swaynag.details.button_details);
127 swaynag_destroy(&swaynag); 130 swaynag_destroy(&swaynag);
128 return exit_code; 131 return exit_code;
129} 132}