aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--include/sway/commands.h1
-rw-r--r--include/sway/criteria.h9
-rw-r--r--include/sway/debug.h23
-rw-r--r--include/sway/desktop.h5
-rw-r--r--include/sway/input/seat.h23
-rw-r--r--include/sway/output.h14
-rw-r--r--include/sway/server.h3
-rw-r--r--include/sway/tree/container.h33
-rw-r--r--include/sway/tree/layout.h12
-rw-r--r--include/sway/tree/root.h15
-rw-r--r--include/sway/tree/view.h11
-rw-r--r--include/sway/tree/workspace.h18
-rw-r--r--include/swaynag/swaynag.h2
-rw-r--r--sway/commands.c1
-rw-r--r--sway/commands/assign.c27
-rw-r--r--sway/commands/exec_always.c11
-rw-r--r--sway/commands/floating.c5
-rw-r--r--sway/commands/focus.c13
-rw-r--r--sway/commands/fullscreen.c3
-rw-r--r--sway/commands/hide_edge_borders.c2
-rw-r--r--sway/commands/move.c18
-rw-r--r--sway/commands/nop.c5
-rw-r--r--sway/commands/rename.c11
-rw-r--r--sway/commands/resize.c9
-rw-r--r--sway/commands/scratchpad.c9
-rw-r--r--sway/commands/set.c12
-rw-r--r--sway/commands/show_marks.c3
-rw-r--r--sway/commands/sticky.c16
-rw-r--r--sway/commands/swap.c8
-rw-r--r--sway/commands/unmark.c3
-rw-r--r--sway/commands/workspace.c7
-rw-r--r--sway/config.c13
-rw-r--r--sway/criteria.c15
-rw-r--r--sway/debug-tree.c10
-rw-r--r--sway/desktop/desktop.c18
-rw-r--r--sway/desktop/output.c51
-rw-r--r--sway/desktop/render.c51
-rw-r--r--sway/desktop/transaction.c118
-rw-r--r--sway/desktop/xdg_shell.c20
-rw-r--r--sway/desktop/xdg_shell_v6.c21
-rw-r--r--sway/desktop/xwayland.c34
-rw-r--r--sway/input/cursor.c36
-rw-r--r--sway/input/seat.c159
-rw-r--r--sway/ipc-json.c22
-rw-r--r--sway/ipc-server.c8
-rw-r--r--sway/main.c20
-rw-r--r--sway/meson.build1
-rw-r--r--sway/server.c7
-rw-r--r--sway/sway.5.scd19
-rw-r--r--sway/tree/arrange.c30
-rw-r--r--sway/tree/container.c255
-rw-r--r--sway/tree/layout.c66
-rw-r--r--sway/tree/output.c73
-rw-r--r--sway/tree/root.c80
-rw-r--r--sway/tree/view.c120
-rw-r--r--sway/tree/workspace.c146
-rw-r--r--swaynag/config.c4
-rw-r--r--swaynag/main.c15
58 files changed, 1076 insertions, 668 deletions
diff --git a/include/sway/commands.h b/include/sway/commands.h
index 545b21e6..8e91c158 100644
--- a/include/sway/commands.h
+++ b/include/sway/commands.h
@@ -136,6 +136,7 @@ sway_cmd cmd_mark;
136sway_cmd cmd_mode; 136sway_cmd cmd_mode;
137sway_cmd cmd_mouse_warping; 137sway_cmd cmd_mouse_warping;
138sway_cmd cmd_move; 138sway_cmd cmd_move;
139sway_cmd cmd_nop;
139sway_cmd cmd_opacity; 140sway_cmd cmd_opacity;
140sway_cmd cmd_new_float; 141sway_cmd cmd_new_float;
141sway_cmd cmd_new_window; 142sway_cmd cmd_new_window;
diff --git a/include/sway/criteria.h b/include/sway/criteria.h
index b4ff7d49..7a1e547b 100644
--- a/include/sway/criteria.h
+++ b/include/sway/criteria.h
@@ -7,10 +7,11 @@
7#include "tree/view.h" 7#include "tree/view.h"
8 8
9enum criteria_type { 9enum criteria_type {
10 CT_COMMAND = 1 << 0, 10 CT_COMMAND = 1 << 0,
11 CT_ASSIGN_OUTPUT = 1 << 1, 11 CT_ASSIGN_OUTPUT = 1 << 1,
12 CT_ASSIGN_WORKSPACE = 1 << 2, 12 CT_ASSIGN_WORKSPACE = 1 << 2,
13 CT_NO_FOCUS = 1 << 3, 13 CT_ASSIGN_WORKSPACE_NUMBER = 1 << 3,
14 CT_NO_FOCUS = 1 << 4,
14}; 15};
15 16
16struct criteria { 17struct criteria {
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/desktop.h b/include/sway/desktop.h
index 348fb187..c969a76b 100644
--- a/include/sway/desktop.h
+++ b/include/sway/desktop.h
@@ -1,8 +1,13 @@
1#include <wlr/types/wlr_surface.h> 1#include <wlr/types/wlr_surface.h>
2 2
3struct sway_container; 3struct sway_container;
4struct sway_view;
4 5
5void desktop_damage_surface(struct wlr_surface *surface, double lx, double ly, 6void desktop_damage_surface(struct wlr_surface *surface, double lx, double ly,
6 bool whole); 7 bool whole);
7 8
8void desktop_damage_whole_container(struct sway_container *con); 9void desktop_damage_whole_container(struct sway_container *con);
10
11void desktop_damage_box(struct wlr_box *box);
12
13void desktop_damage_view(struct sway_view *view);
diff --git a/include/sway/input/seat.h b/include/sway/input/seat.h
index eb4202f3..5c404ecd 100644
--- a/include/sway/input/seat.h
+++ b/include/sway/input/seat.h
@@ -35,6 +35,14 @@ struct sway_drag_icon {
35 struct wl_listener destroy; 35 struct wl_listener destroy;
36}; 36};
37 37
38enum sway_seat_operation {
39 OP_NONE,
40 OP_DOWN,
41 OP_MOVE,
42 OP_RESIZE_FLOATING,
43 OP_RESIZE_TILING,
44};
45
38struct sway_seat { 46struct sway_seat {
39 struct wlr_seat *wlr_seat; 47 struct wlr_seat *wlr_seat;
40 struct sway_cursor *cursor; 48 struct sway_cursor *cursor;
@@ -54,13 +62,7 @@ struct sway_seat {
54 double touch_x, touch_y; 62 double touch_x, touch_y;
55 63
56 // Operations (drag and resize) 64 // Operations (drag and resize)
57 enum { 65 enum sway_seat_operation operation;
58 OP_NONE,
59 OP_MOVE,
60 OP_RESIZE_FLOATING,
61 OP_RESIZE_TILING,
62 } operation;
63
64 struct sway_container *op_container; 66 struct sway_container *op_container;
65 enum wlr_edges op_resize_edge; 67 enum wlr_edges op_resize_edge;
66 uint32_t op_button; 68 uint32_t op_button;
@@ -68,6 +70,7 @@ struct sway_seat {
68 double op_ref_lx, op_ref_ly; // cursor's x/y at start of op 70 double op_ref_lx, op_ref_ly; // cursor's x/y at start of op
69 double op_ref_width, op_ref_height; // container's size at start of op 71 double op_ref_width, op_ref_height; // container's size at start of op
70 double op_ref_con_lx, op_ref_con_ly; // container's x/y at start of op 72 double op_ref_con_lx, op_ref_con_ly; // container's x/y at start of op
73 bool op_moved; // if the mouse moved during a down op
71 74
72 uint32_t last_button; 75 uint32_t last_button;
73 uint32_t last_button_serial; 76 uint32_t last_button_serial;
@@ -157,6 +160,9 @@ bool seat_is_input_allowed(struct sway_seat *seat, struct wlr_surface *surface);
157 160
158void drag_icon_update_position(struct sway_drag_icon *icon); 161void drag_icon_update_position(struct sway_drag_icon *icon);
159 162
163void seat_begin_down(struct sway_seat *seat, struct sway_container *con,
164 uint32_t button, double sx, double sy);
165
160void seat_begin_move(struct sway_seat *seat, struct sway_container *con, 166void seat_begin_move(struct sway_seat *seat, struct sway_container *con,
161 uint32_t button); 167 uint32_t button);
162 168
@@ -166,6 +172,9 @@ void seat_begin_resize_floating(struct sway_seat *seat,
166void seat_begin_resize_tiling(struct sway_seat *seat, 172void seat_begin_resize_tiling(struct sway_seat *seat,
167 struct sway_container *con, uint32_t button, enum wlr_edges edge); 173 struct sway_container *con, uint32_t button, enum wlr_edges edge);
168 174
175struct sway_container *seat_get_focus_inactive_floating(struct sway_seat *seat,
176 struct sway_container *container);
177
169void seat_end_mouse_operation(struct sway_seat *seat); 178void seat_end_mouse_operation(struct sway_seat *seat);
170 179
171void seat_pointer_notify_button(struct sway_seat *seat, uint32_t time_msec, 180void seat_pointer_notify_button(struct sway_seat *seat, uint32_t time_msec,
diff --git a/include/sway/output.h b/include/sway/output.h
index 80dcd37b..d0d034b3 100644
--- a/include/sway/output.h
+++ b/include/sway/output.h
@@ -58,6 +58,8 @@ void output_damage_whole_container(struct sway_output *output,
58 58
59struct sway_container *output_by_name(const char *name); 59struct sway_container *output_by_name(const char *name);
60 60
61void output_sort_workspaces(struct sway_container *output);
62
61void output_enable(struct sway_output *output); 63void output_enable(struct sway_output *output);
62 64
63bool output_has_opaque_overlay_layer_surface(struct sway_output *output); 65bool output_has_opaque_overlay_layer_surface(struct sway_output *output);
@@ -93,4 +95,16 @@ void output_drag_icons_for_each_surface(struct sway_output *output,
93 struct wl_list *drag_icons, sway_surface_iterator_func_t iterator, 95 struct wl_list *drag_icons, sway_surface_iterator_func_t iterator,
94 void *user_data); 96 void *user_data);
95 97
98void output_for_each_workspace(struct sway_container *output,
99 void (*f)(struct sway_container *con, void *data), void *data);
100
101void output_for_each_container(struct sway_container *output,
102 void (*f)(struct sway_container *con, void *data), void *data);
103
104struct sway_container *output_find_workspace(struct sway_container *output,
105 bool (*test)(struct sway_container *con, void *data), void *data);
106
107struct sway_container *output_find_container(struct sway_container *output,
108 bool (*test)(struct sway_container *con, void *data), void *data);
109
96#endif 110#endif
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/container.h b/include/sway/tree/container.h
index b64a2e63..cd886cd0 100644
--- a/include/sway/tree/container.h
+++ b/include/sway/tree/container.h
@@ -40,7 +40,6 @@ enum sway_container_layout {
40 L_VERT, 40 L_VERT,
41 L_STACKED, 41 L_STACKED,
42 L_TABBED, 42 L_TABBED,
43 L_FLOATING,
44}; 43};
45 44
46enum sway_container_border { 45enum sway_container_border {
@@ -83,10 +82,11 @@ struct sway_container_state {
83 bool border_bottom; 82 bool border_bottom;
84 bool border_left; 83 bool border_left;
85 bool border_right; 84 bool border_right;
85 bool using_csd;
86 86
87 // Workspace properties 87 // Workspace properties
88 struct sway_container *ws_fullscreen; 88 struct sway_container *ws_fullscreen;
89 struct sway_container *ws_floating; 89 list_t *ws_floating;
90}; 90};
91 91
92struct sway_container { 92struct sway_container {
@@ -138,6 +138,9 @@ struct sway_container {
138 138
139 struct sway_container *parent; 139 struct sway_container *parent;
140 140
141 // Outputs currently being intersected
142 list_t *outputs; // struct sway_output
143
141 // Indicates that the container is a scratchpad container. 144 // Indicates that the container is a scratchpad container.
142 // Both hidden and visible scratchpad containers have scratchpad=true. 145 // Both hidden and visible scratchpad containers have scratchpad=true.
143 // Hidden scratchpad containers have a NULL parent. 146 // Hidden scratchpad containers have a NULL parent.
@@ -166,12 +169,7 @@ struct sway_container {
166 169
167 struct { 170 struct {
168 struct wl_signal destroy; 171 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; 172 } events;
173
174 struct wl_listener reparent;
175}; 173};
176 174
177struct sway_container *container_create(enum sway_container_type type); 175struct sway_container *container_create(enum sway_container_type type);
@@ -213,15 +211,11 @@ struct sway_container *container_destroy(struct sway_container *container);
213 211
214struct sway_container *container_close(struct sway_container *container); 212struct sway_container *container_close(struct sway_container *container);
215 213
216void container_descendants(struct sway_container *root,
217 enum sway_container_type type,
218 void (*func)(struct sway_container *item, void *data), void *data);
219
220/** 214/**
221 * Search a container's descendants a container based on test criteria. Returns 215 * Search a container's descendants a container based on test criteria. Returns
222 * the first container that passes the test. 216 * the first container that passes the test.
223 */ 217 */
224struct sway_container *container_find(struct sway_container *container, 218struct sway_container *container_find_child(struct sway_container *container,
225 bool (*test)(struct sway_container *view, void *data), void *data); 219 bool (*test)(struct sway_container *view, void *data), void *data);
226 220
227/** 221/**
@@ -243,10 +237,7 @@ struct sway_container *tiling_container_at(
243 struct sway_container *con, double lx, double ly, 237 struct sway_container *con, double lx, double ly,
244 struct wlr_surface **surface, double *sx, double *sy); 238 struct wlr_surface **surface, double *sx, double *sy);
245 239
246/** 240void container_for_each_child(struct sway_container *container,
247 * Apply the function for each child of the container depth first.
248 */
249void container_for_each_descendant(struct sway_container *container,
250 void (*f)(struct sway_container *container, void *data), void *data); 241 void (*f)(struct sway_container *container, void *data), void *data);
251 242
252/** 243/**
@@ -361,11 +352,11 @@ bool container_is_floating_or_child(struct sway_container *container);
361bool container_is_fullscreen_or_child(struct sway_container *container); 352bool container_is_fullscreen_or_child(struct sway_container *container);
362 353
363/** 354/**
364 * Wrap the children of parent in a new container. The new container will be the 355 * Return the output which will be used for scale purposes.
365 * only child of parent. 356 * This is the most recently entered output.
366 *
367 * The new container is returned.
368 */ 357 */
369struct sway_container *container_wrap_children(struct sway_container *parent); 358struct sway_output *container_get_effective_output(struct sway_container *con);
359
360void container_discover_outputs(struct sway_container *con);
370 361
371#endif 362#endif
diff --git a/include/sway/tree/layout.h b/include/sway/tree/layout.h
index 77cd954b..519189d9 100644
--- a/include/sway/tree/layout.h
+++ b/include/sway/tree/layout.h
@@ -15,13 +15,7 @@ enum movement_direction {
15 MOVE_CHILD, 15 MOVE_CHILD,
16}; 16};
17 17
18enum resize_edge { 18enum wlr_edges;
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 19
26struct sway_container; 20struct sway_container;
27 21
@@ -45,8 +39,6 @@ void container_move(struct sway_container *container,
45enum sway_container_layout container_get_default_layout( 39enum sway_container_layout container_get_default_layout(
46 struct sway_container *con); 40 struct sway_container *con);
47 41
48void container_sort_workspaces(struct sway_container *output);
49
50struct sway_container *container_get_in_direction(struct sway_container 42struct sway_container *container_get_in_direction(struct sway_container
51 *container, struct sway_seat *seat, enum movement_direction dir); 43 *container, struct sway_seat *seat, enum movement_direction dir);
52 44
@@ -54,7 +46,7 @@ struct sway_container *container_split(struct sway_container *child,
54 enum sway_container_layout layout); 46 enum sway_container_layout layout);
55 47
56void container_recursive_resize(struct sway_container *container, 48void container_recursive_resize(struct sway_container *container,
57 double amount, enum resize_edge edge); 49 double amount, enum wlr_edges edge);
58 50
59void container_swap(struct sway_container *con1, struct sway_container *con2); 51void container_swap(struct sway_container *con1, struct sway_container *con2);
60 52
diff --git a/include/sway/tree/root.h b/include/sway/tree/root.h
index edb7c817..d1f04a96 100644
--- a/include/sway/tree/root.h
+++ b/include/sway/tree/root.h
@@ -58,4 +58,19 @@ struct sway_container *root_workspace_for_pid(pid_t pid);
58 58
59void root_record_workspace_pid(pid_t pid); 59void root_record_workspace_pid(pid_t pid);
60 60
61void root_for_each_workspace(void (*f)(struct sway_container *con, void *data),
62 void *data);
63
64void root_for_each_container(void (*f)(struct sway_container *con, void *data),
65 void *data);
66
67struct sway_container *root_find_output(
68 bool (*test)(struct sway_container *con, void *data), void *data);
69
70struct sway_container *root_find_workspace(
71 bool (*test)(struct sway_container *con, void *data), void *data);
72
73struct sway_container *root_find_container(
74 bool (*test)(struct sway_container *con, void *data), void *data);
75
61#endif 76#endif
diff --git a/include/sway/tree/view.h b/include/sway/tree/view.h
index c2225bcb..5fdecc2b 100644
--- a/include/sway/tree/view.h
+++ b/include/sway/tree/view.h
@@ -88,6 +88,14 @@ struct sway_view {
88 struct wlr_buffer *saved_buffer; 88 struct wlr_buffer *saved_buffer;
89 int saved_buffer_width, saved_buffer_height; 89 int saved_buffer_width, saved_buffer_height;
90 90
91 // The geometry for whatever the client is committing, regardless of
92 // transaction state. Updated on every commit.
93 struct wlr_box geometry;
94
95 // The "old" geometry during a transaction. Used to damage the old location
96 // when a transaction is applied.
97 struct wlr_box saved_geometry;
98
91 bool destroying; 99 bool destroying;
92 100
93 list_t *executed_criteria; // struct criteria * 101 list_t *executed_criteria; // struct criteria *
@@ -112,7 +120,6 @@ struct sway_view {
112 } events; 120 } events;
113 121
114 struct wl_listener surface_new_subsurface; 122 struct wl_listener surface_new_subsurface;
115 struct wl_listener container_reparent;
116}; 123};
117 124
118struct sway_xdg_shell_v6_view { 125struct sway_xdg_shell_v6_view {
@@ -285,8 +292,6 @@ void view_map(struct sway_view *view, struct wlr_surface *wlr_surface);
285 292
286void view_unmap(struct sway_view *view); 293void view_unmap(struct sway_view *view);
287 294
288void view_update_position(struct sway_view *view, double lx, double ly);
289
290void view_update_size(struct sway_view *view, int width, int height); 295void view_update_size(struct sway_view *view, int width, int height);
291 296
292void view_child_init(struct sway_view_child *child, 297void view_child_init(struct sway_view_child *child,
diff --git a/include/sway/tree/workspace.h b/include/sway/tree/workspace.h
index 056f2329..35c91017 100644
--- a/include/sway/tree/workspace.h
+++ b/include/sway/tree/workspace.h
@@ -9,7 +9,7 @@ struct sway_view;
9struct sway_workspace { 9struct sway_workspace {
10 struct sway_container *swayc; 10 struct sway_container *swayc;
11 struct sway_container *fullscreen; 11 struct sway_container *fullscreen;
12 struct sway_container *floating; 12 list_t *floating; // struct sway_container
13 list_t *output_priority; 13 list_t *output_priority;
14 bool urgent; 14 bool urgent;
15}; 15};
@@ -50,4 +50,20 @@ struct sway_container *workspace_output_get_highest_available(
50 50
51void workspace_detect_urgent(struct sway_container *workspace); 51void workspace_detect_urgent(struct sway_container *workspace);
52 52
53void workspace_for_each_container(struct sway_container *ws,
54 void (*f)(struct sway_container *con, void *data), void *data);
55
56struct sway_container *workspace_find_container(struct sway_container *ws,
57 bool (*test)(struct sway_container *con, void *data), void *data);
58
59/**
60 * Wrap the workspace's tiling children in a new container.
61 * The new container will be the only direct tiling child of the workspace.
62 * The new container is returned.
63 */
64struct sway_container *workspace_wrap_children(struct sway_container *ws);
65
66void workspace_add_floating(struct sway_container *workspace,
67 struct sway_container *con);
68
53#endif 69#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/sway/commands.c b/sway/commands.c
index 364c26da..d9c54adc 100644
--- a/sway/commands.c
+++ b/sway/commands.c
@@ -146,6 +146,7 @@ static struct cmd_handler command_handlers[] = {
146 { "layout", cmd_layout }, 146 { "layout", cmd_layout },
147 { "mark", cmd_mark }, 147 { "mark", cmd_mark },
148 { "move", cmd_move }, 148 { "move", cmd_move },
149 { "nop", cmd_nop },
149 { "opacity", cmd_opacity }, 150 { "opacity", cmd_opacity },
150 { "reload", cmd_reload }, 151 { "reload", cmd_reload },
151 { "rename", cmd_rename }, 152 { "rename", cmd_rename },
diff --git a/sway/commands/assign.c b/sway/commands/assign.c
index 0bc0929a..04582e88 100644
--- a/sway/commands/assign.c
+++ b/sway/commands/assign.c
@@ -22,27 +22,38 @@ struct cmd_results *cmd_assign(int argc, char **argv) {
22 return error; 22 return error;
23 } 23 }
24 24
25 ++argv; 25 --argc; ++argv;
26 int target_len = argc - 1;
27 26
28 if (strncmp(*argv, "→", strlen("→")) == 0) { 27 if (strncmp(*argv, "→", strlen("→")) == 0) {
29 if (argc < 3) { 28 if (argc < 2) {
30 free(criteria); 29 free(criteria);
31 return cmd_results_new(CMD_INVALID, "assign", "Missing workspace"); 30 return cmd_results_new(CMD_INVALID, "assign", "Missing workspace");
32 } 31 }
32 --argc;
33 ++argv; 33 ++argv;
34 --target_len;
35 } 34 }
36 35
37 if (strcmp(*argv, "output") == 0) { 36 if (strcmp(*argv, "output") == 0) {
38 criteria->type = CT_ASSIGN_OUTPUT; 37 criteria->type = CT_ASSIGN_OUTPUT;
39 ++argv; 38 --argc; ++argv;
40 --target_len;
41 } else { 39 } else {
42 criteria->type = CT_ASSIGN_WORKSPACE; 40 if (strcmp(*argv, "workspace") == 0) {
41 --argc; ++argv;
42 }
43 if (strcmp(*argv, "number") == 0) {
44 --argc; ++argv;
45 if (argv[0][0] < '0' || argv[0][0] > '9') {
46 free(criteria);
47 return cmd_results_new(CMD_INVALID, "assign",
48 "Invalid workspace number '%s'", argv[0]);
49 }
50 criteria->type = CT_ASSIGN_WORKSPACE_NUMBER;
51 } else {
52 criteria->type = CT_ASSIGN_WORKSPACE;
53 }
43 } 54 }
44 55
45 criteria->target = join_args(argv, target_len); 56 criteria->target = join_args(argv, argc);
46 57
47 list_add(config->criteria, criteria); 58 list_add(config->criteria, criteria);
48 wlr_log(WLR_DEBUG, "assign: '%s' -> '%s' added", criteria->raw, 59 wlr_log(WLR_DEBUG, "assign: '%s' -> '%s' added", criteria->raw,
diff --git a/sway/commands/exec_always.c b/sway/commands/exec_always.c
index 00e39ae7..5ce7919b 100644
--- a/sway/commands/exec_always.c
+++ b/sway/commands/exec_always.c
@@ -26,7 +26,16 @@ struct cmd_results *cmd_exec_always(int argc, char **argv) {
26 return error; 26 return error;
27 } 27 }
28 28
29 tmp = join_args(argv + 1, argc - 1); 29 --argc; ++argv;
30 }
31
32 if (argv[0][0] == '\'' || argv[0][0] == '"') {
33 if (argc > 0) {
34 return cmd_results_new(CMD_INVALID, "exec_always",
35 "command cannot be partially quoted");
36 }
37 tmp = strdup(argv[0]);
38 strip_quotes(tmp);
30 } else { 39 } else {
31 tmp = join_args(argv, argc); 40 tmp = join_args(argv, argc);
32 } 41 }
diff --git a/sway/commands/floating.c b/sway/commands/floating.c
index 31de5ec3..beafd9fb 100644
--- a/sway/commands/floating.c
+++ b/sway/commands/floating.c
@@ -8,6 +8,7 @@
8#include "sway/tree/container.h" 8#include "sway/tree/container.h"
9#include "sway/tree/layout.h" 9#include "sway/tree/layout.h"
10#include "sway/tree/view.h" 10#include "sway/tree/view.h"
11#include "sway/tree/workspace.h"
11#include "list.h" 12#include "list.h"
12 13
13struct cmd_results *cmd_floating(int argc, char **argv) { 14struct cmd_results *cmd_floating(int argc, char **argv) {
@@ -24,7 +25,7 @@ struct cmd_results *cmd_floating(int argc, char **argv) {
24 if (container->type == C_WORKSPACE) { 25 if (container->type == C_WORKSPACE) {
25 // Wrap the workspace's children in a container so we can float it 26 // Wrap the workspace's children in a container so we can float it
26 struct sway_container *workspace = container; 27 struct sway_container *workspace = container;
27 container = container_wrap_children(container); 28 container = workspace_wrap_children(container);
28 workspace->layout = L_HORIZ; 29 workspace->layout = L_HORIZ;
29 seat_set_focus(config->handler_context.seat, container); 30 seat_set_focus(config->handler_context.seat, container);
30 } 31 }
@@ -32,7 +33,7 @@ struct cmd_results *cmd_floating(int argc, char **argv) {
32 // If the container is in a floating split container, 33 // If the container is in a floating split container,
33 // operate on the split container instead of the child. 34 // operate on the split container instead of the child.
34 if (container_is_floating_or_child(container)) { 35 if (container_is_floating_or_child(container)) {
35 while (container->parent->layout != L_FLOATING) { 36 while (container->parent->type != C_WORKSPACE) {
36 container = container->parent; 37 container = container->parent;
37 } 38 }
38 } 39 }
diff --git a/sway/commands/focus.c b/sway/commands/focus.c
index 76d3f1dc..6659a683 100644
--- a/sway/commands/focus.c
+++ b/sway/commands/focus.c
@@ -39,21 +39,24 @@ static struct cmd_results *focus_mode(struct sway_container *con,
39 // If the container is in a floating split container, 39 // If the container is in a floating split container,
40 // operate on the split container instead of the child. 40 // operate on the split container instead of the child.
41 if (container_is_floating_or_child(con)) { 41 if (container_is_floating_or_child(con)) {
42 while (con->parent->layout != L_FLOATING) { 42 while (con->parent->type != C_WORKSPACE) {
43 con = con->parent; 43 con = con->parent;
44 } 44 }
45 } 45 }
46 46
47 struct sway_container *new_focus = NULL; 47 struct sway_container *new_focus = NULL;
48 if (floating) { 48 if (floating) {
49 new_focus = seat_get_focus_inactive(seat, ws->sway_workspace->floating); 49 new_focus = seat_get_focus_inactive_floating(seat, ws);
50 } else { 50 } else {
51 new_focus = seat_get_focus_inactive_tiling(seat, ws); 51 new_focus = seat_get_focus_inactive_tiling(seat, ws);
52 } 52 }
53 if (!new_focus) { 53 if (new_focus) {
54 new_focus = ws; 54 seat_set_focus(seat, new_focus);
55 } else {
56 return cmd_results_new(CMD_FAILURE, "focus",
57 "Failed to find a %s container in workspace",
58 floating ? "floating" : "tiling");
55 } 59 }
56 seat_set_focus(seat, new_focus);
57 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 60 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
58} 61}
59 62
diff --git a/sway/commands/fullscreen.c b/sway/commands/fullscreen.c
index 5ad06e40..a0661200 100644
--- a/sway/commands/fullscreen.c
+++ b/sway/commands/fullscreen.c
@@ -4,6 +4,7 @@
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/workspace.h"
7#include "sway/tree/layout.h" 8#include "sway/tree/layout.h"
8#include "util.h" 9#include "util.h"
9 10
@@ -21,7 +22,7 @@ struct cmd_results *cmd_fullscreen(int argc, char **argv) {
21 if (container->type == C_WORKSPACE) { 22 if (container->type == C_WORKSPACE) {
22 // Wrap the workspace's children in a container so we can fullscreen it 23 // Wrap the workspace's children in a container so we can fullscreen it
23 struct sway_container *workspace = container; 24 struct sway_container *workspace = container;
24 container = container_wrap_children(container); 25 container = workspace_wrap_children(container);
25 workspace->layout = L_HORIZ; 26 workspace->layout = L_HORIZ;
26 seat_set_focus(config->handler_context.seat, container); 27 seat_set_focus(config->handler_context.seat, container);
27 } 28 }
diff --git a/sway/commands/hide_edge_borders.c b/sway/commands/hide_edge_borders.c
index bb390f5f..d59c9fdb 100644
--- a/sway/commands/hide_edge_borders.c
+++ b/sway/commands/hide_edge_borders.c
@@ -31,7 +31,7 @@ struct cmd_results *cmd_hide_edge_borders(int argc, char **argv) {
31 "<none|vertical|horizontal|both|smart>'"); 31 "<none|vertical|horizontal|both|smart>'");
32 } 32 }
33 33
34 container_for_each_descendant(&root_container, _configure_view, NULL); 34 root_for_each_container(_configure_view, NULL);
35 35
36 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 36 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
37} 37}
diff --git a/sway/commands/move.c b/sway/commands/move.c
index de6b1b0a..c6dc0775 100644
--- a/sway/commands/move.c
+++ b/sway/commands/move.c
@@ -1,4 +1,5 @@
1#define _XOPEN_SOURCE 500 1#define _XOPEN_SOURCE 500
2#include <ctype.h>
2#include <stdbool.h> 3#include <stdbool.h>
3#include <string.h> 4#include <string.h>
4#include <strings.h> 5#include <strings.h>
@@ -22,7 +23,7 @@
22static const char *expected_syntax = 23static const char *expected_syntax =
23 "Expected 'move <left|right|up|down> <[px] px>' or " 24 "Expected 'move <left|right|up|down> <[px] px>' or "
24 "'move [--no-auto-back-and-forth] <container|window> [to] workspace <name>' or " 25 "'move [--no-auto-back-and-forth] <container|window> [to] workspace <name>' or "
25 "'move [--no-auto-back-and-forth] <container|window|workspace> [to] output <name|direction>' or " 26 "'move <container|window|workspace> [to] output <name|direction>' or "
26 "'move <container|window> [to] mark <mark>'"; 27 "'move <container|window> [to] mark <mark>'";
27 28
28static struct sway_container *output_in_direction(const char *direction, 29static struct sway_container *output_in_direction(const char *direction,
@@ -64,7 +65,7 @@ static struct cmd_results *cmd_move_container(struct sway_container *current,
64 return cmd_results_new(CMD_FAILURE, "move", 65 return cmd_results_new(CMD_FAILURE, "move",
65 "Can't move an empty workspace"); 66 "Can't move an empty workspace");
66 } 67 }
67 current = container_wrap_children(current); 68 current = workspace_wrap_children(current);
68 } else if (current->type != C_CONTAINER && current->type != C_VIEW) { 69 } else if (current->type != C_CONTAINER && current->type != C_VIEW) {
69 return cmd_results_new(CMD_FAILURE, "move", 70 return cmd_results_new(CMD_FAILURE, "move",
70 "Can only move containers and views."); 71 "Can only move containers and views.");
@@ -124,7 +125,11 @@ static struct cmd_results *cmd_move_container(struct sway_container *current,
124 return cmd_results_new(CMD_INVALID, "move", 125 return cmd_results_new(CMD_INVALID, "move",
125 expected_syntax); 126 expected_syntax);
126 } 127 }
127 ws_name = strdup(argv[3]); 128 if (!isdigit(argv[3][0])) {
129 return cmd_results_new(CMD_INVALID, "move",
130 "Invalid workspace number '%s'", argv[3]);
131 }
132 ws_name = join_args(argv + 3, argc - 3);
128 ws = workspace_by_number(ws_name); 133 ws = workspace_by_number(ws_name);
129 } else { 134 } else {
130 ws_name = join_args(argv + 2, argc - 2); 135 ws_name = join_args(argv + 2, argc - 2);
@@ -231,7 +236,6 @@ static void workspace_move_to_output(struct sway_container *workspace,
231 seat_get_focus_inactive(seat, output); 236 seat_get_focus_inactive(seat, output);
232 237
233 container_add_child(output, workspace); 238 container_add_child(output, workspace);
234 wl_signal_emit(&workspace->events.reparent, old_output);
235 239
236 // If moving the last workspace from the old output, create a new workspace 240 // If moving the last workspace from the old output, create a new workspace
237 // on the old output 241 // on the old output
@@ -245,7 +249,7 @@ static void workspace_move_to_output(struct sway_container *workspace,
245 // Try to remove an empty workspace from the destination output. 249 // Try to remove an empty workspace from the destination output.
246 container_reap_empty_recursive(new_output_focus); 250 container_reap_empty_recursive(new_output_focus);
247 251
248 container_sort_workspaces(output); 252 output_sort_workspaces(output);
249 seat_set_focus(seat, output); 253 seat_set_focus(seat, output);
250 workspace_output_raise_priority(workspace, old_output, output); 254 workspace_output_raise_priority(workspace, old_output, output);
251 ipc_event_workspace(NULL, workspace, "move"); 255 ipc_event_workspace(NULL, workspace, "move");
@@ -437,14 +441,14 @@ static struct cmd_results *move_to_scratchpad(struct sway_container *con) {
437 if (con->type == C_WORKSPACE) { 441 if (con->type == C_WORKSPACE) {
438 // Wrap the workspace's children in a container 442 // Wrap the workspace's children in a container
439 struct sway_container *workspace = con; 443 struct sway_container *workspace = con;
440 con = container_wrap_children(con); 444 con = workspace_wrap_children(con);
441 workspace->layout = L_HORIZ; 445 workspace->layout = L_HORIZ;
442 } 446 }
443 447
444 // If the container is in a floating split container, 448 // If the container is in a floating split container,
445 // operate on the split container instead of the child. 449 // operate on the split container instead of the child.
446 if (container_is_floating_or_child(con)) { 450 if (container_is_floating_or_child(con)) {
447 while (con->parent->layout != L_FLOATING) { 451 while (con->parent->type != C_WORKSPACE) {
448 con = con->parent; 452 con = con->parent;
449 } 453 }
450 } 454 }
diff --git a/sway/commands/nop.c b/sway/commands/nop.c
new file mode 100644
index 00000000..c12fe15a
--- /dev/null
+++ b/sway/commands/nop.c
@@ -0,0 +1,5 @@
1#include "sway/commands.h"
2
3struct cmd_results *cmd_nop(int argc, char **argv) {
4 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
5}
diff --git a/sway/commands/rename.c b/sway/commands/rename.c
index c6952bbb..21d2aa64 100644
--- a/sway/commands/rename.c
+++ b/sway/commands/rename.c
@@ -1,4 +1,5 @@
1#define _XOPEN_SOURCE 500 1#define _XOPEN_SOURCE 500
2#include <ctype.h>
2#include <string.h> 3#include <string.h>
3#include <strings.h> 4#include <strings.h>
4#include "log.h" 5#include "log.h"
@@ -6,6 +7,7 @@
6#include "sway/commands.h" 7#include "sway/commands.h"
7#include "sway/config.h" 8#include "sway/config.h"
8#include "sway/ipc-server.h" 9#include "sway/ipc-server.h"
10#include "sway/output.h"
9#include "sway/tree/container.h" 11#include "sway/tree/container.h"
10#include "sway/tree/workspace.h" 12#include "sway/tree/workspace.h"
11 13
@@ -33,6 +35,10 @@ struct cmd_results *cmd_rename(int argc, char **argv) {
33 } 35 }
34 } else if (strcasecmp(argv[1], "number") == 0) { 36 } else if (strcasecmp(argv[1], "number") == 0) {
35 // 'rename workspace number x to new_name' 37 // 'rename workspace number x to new_name'
38 if (!isdigit(argv[2][0])) {
39 return cmd_results_new(CMD_INVALID, "rename",
40 "Invalid workspace number '%s'", argv[2]);
41 }
36 workspace = workspace_by_number(argv[2]); 42 workspace = workspace_by_number(argv[2]);
37 while (argn < argc && strcasecmp(argv[argn], "to") != 0) { 43 while (argn < argc && strcasecmp(argv[argn], "to") != 0) {
38 ++argn; 44 ++argn;
@@ -66,7 +72,8 @@ struct cmd_results *cmd_rename(int argc, char **argv) {
66 strcasecmp(new_name, "next_on_output") == 0 || 72 strcasecmp(new_name, "next_on_output") == 0 ||
67 strcasecmp(new_name, "prev_on_output") == 0 || 73 strcasecmp(new_name, "prev_on_output") == 0 ||
68 strcasecmp(new_name, "back_and_forth") == 0 || 74 strcasecmp(new_name, "back_and_forth") == 0 ||
69 strcasecmp(new_name, "current") == 0) { 75 strcasecmp(new_name, "current") == 0 ||
76 strcasecmp(new_name, "number") == 0) {
70 free(new_name); 77 free(new_name);
71 return cmd_results_new(CMD_INVALID, "rename", 78 return cmd_results_new(CMD_INVALID, "rename",
72 "Cannot use special workspace name '%s'", argv[argn]); 79 "Cannot use special workspace name '%s'", argv[argn]);
@@ -82,7 +89,7 @@ struct cmd_results *cmd_rename(int argc, char **argv) {
82 free(workspace->name); 89 free(workspace->name);
83 workspace->name = new_name; 90 workspace->name = new_name;
84 91
85 container_sort_workspaces(workspace->parent); 92 output_sort_workspaces(workspace->parent);
86 ipc_event_workspace(NULL, workspace, "rename"); 93 ipc_event_workspace(NULL, workspace, "rename");
87 94
88 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 95 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
diff --git a/sway/commands/resize.c b/sway/commands/resize.c
index 0f3005f4..ea1e36ff 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"
@@ -250,10 +251,10 @@ static void resize_tiled(struct sway_container *parent, int amount,
250 } 251 }
251 } 252 }
252 253
253 enum resize_edge minor_edge = axis == RESIZE_AXIS_HORIZONTAL ? 254 enum wlr_edges minor_edge = axis == RESIZE_AXIS_HORIZONTAL ?
254 RESIZE_EDGE_LEFT : RESIZE_EDGE_TOP; 255 WLR_EDGE_LEFT : WLR_EDGE_TOP;
255 enum resize_edge major_edge = axis == RESIZE_AXIS_HORIZONTAL ? 256 enum wlr_edges major_edge = axis == RESIZE_AXIS_HORIZONTAL ?
256 RESIZE_EDGE_RIGHT : RESIZE_EDGE_BOTTOM; 257 WLR_EDGE_RIGHT : WLR_EDGE_BOTTOM;
257 258
258 for (int i = 0; i < parent->parent->children->length; i++) { 259 for (int i = 0; i < parent->parent->children->length; i++) {
259 struct sway_container *sibling = parent->parent->children->items[i]; 260 struct sway_container *sibling = parent->parent->children->items[i];
diff --git a/sway/commands/scratchpad.c b/sway/commands/scratchpad.c
index 0e573aeb..7da20015 100644
--- a/sway/commands/scratchpad.c
+++ b/sway/commands/scratchpad.c
@@ -16,7 +16,7 @@ static void scratchpad_toggle_auto(void) {
16 // If the focus is in a floating split container, 16 // If the focus is in a floating split container,
17 // operate on the split container instead of the child. 17 // operate on the split container instead of the child.
18 if (container_is_floating_or_child(focus)) { 18 if (container_is_floating_or_child(focus)) {
19 while (focus->parent->layout != L_FLOATING) { 19 while (focus->parent->type != C_WORKSPACE) {
20 focus = focus->parent; 20 focus = focus->parent;
21 } 21 }
22 } 22 }
@@ -33,9 +33,8 @@ static void scratchpad_toggle_auto(void) {
33 33
34 // Check if there is an unfocused scratchpad window on the current workspace 34 // Check if there is an unfocused scratchpad window on the current workspace
35 // and focus it. 35 // and focus it.
36 for (int i = 0; i < ws->sway_workspace->floating->children->length; ++i) { 36 for (int i = 0; i < ws->sway_workspace->floating->length; ++i) {
37 struct sway_container *floater = 37 struct sway_container *floater = ws->sway_workspace->floating->items[i];
38 ws->sway_workspace->floating->children->items[i];
39 if (floater->scratchpad && focus != floater) { 38 if (floater->scratchpad && focus != floater) {
40 wlr_log(WLR_DEBUG, 39 wlr_log(WLR_DEBUG,
41 "Focusing other scratchpad window (%s) in this workspace", 40 "Focusing other scratchpad window (%s) in this workspace",
@@ -103,7 +102,7 @@ struct cmd_results *cmd_scratchpad(int argc, char **argv) {
103 // If the container is in a floating split container, 102 // If the container is in a floating split container,
104 // operate on the split container instead of the child. 103 // operate on the split container instead of the child.
105 if (container_is_floating_or_child(con)) { 104 if (container_is_floating_or_child(con)) {
106 while (con->parent->layout != L_FLOATING) { 105 while (con->parent->type != C_WORKSPACE) {
107 con = con->parent; 106 con = con->parent;
108 } 107 }
109 } 108 }
diff --git a/sway/commands/set.c b/sway/commands/set.c
index ea388d3b..be51230b 100644
--- a/sway/commands/set.c
+++ b/sway/commands/set.c
@@ -25,23 +25,13 @@ void free_sway_variable(struct sway_variable *var) {
25} 25}
26 26
27struct cmd_results *cmd_set(int argc, char **argv) { 27struct cmd_results *cmd_set(int argc, char **argv) {
28 char *tmp;
29 struct cmd_results *error = NULL; 28 struct cmd_results *error = NULL;
30 if ((error = checkarg(argc, "set", EXPECTED_AT_LEAST, 2))) { 29 if ((error = checkarg(argc, "set", EXPECTED_AT_LEAST, 2))) {
31 return error; 30 return error;
32 } 31 }
33 32
34 if (argv[0][0] != '$') { 33 if (argv[0][0] != '$') {
35 wlr_log(WLR_INFO, "Warning: variable '%s' doesn't start with $", argv[0]); 34 return cmd_results_new(CMD_INVALID, "set", "variable '%s' must start with $", argv[0]);
36
37 size_t size = snprintf(NULL, 0, "$%s", argv[0]);
38 tmp = malloc(size + 1);
39 if (!tmp) {
40 return cmd_results_new(CMD_FAILURE, "set", "Not possible to create variable $'%s'", argv[0]);
41 }
42 snprintf(tmp, size+1, "$%s", argv[0]);
43
44 argv[0] = tmp;
45 } 35 }
46 36
47 struct sway_variable *var = NULL; 37 struct sway_variable *var = NULL;
diff --git a/sway/commands/show_marks.c b/sway/commands/show_marks.c
index cf153a0a..dd7d170c 100644
--- a/sway/commands/show_marks.c
+++ b/sway/commands/show_marks.c
@@ -24,8 +24,7 @@ struct cmd_results *cmd_show_marks(int argc, char **argv) {
24 config->show_marks = parse_boolean(argv[0], config->show_marks); 24 config->show_marks = parse_boolean(argv[0], config->show_marks);
25 25
26 if (config->show_marks) { 26 if (config->show_marks) {
27 container_for_each_descendant(&root_container, 27 root_for_each_container(rebuild_marks_iterator, NULL);
28 rebuild_marks_iterator, NULL);
29 } 28 }
30 29
31 for (int i = 0; i < root_container.children->length; ++i) { 30 for (int i = 0; i < root_container.children->length; ++i) {
diff --git a/sway/commands/sticky.c b/sway/commands/sticky.c
index 732ccb98..a0dd7215 100644
--- a/sway/commands/sticky.c
+++ b/sway/commands/sticky.c
@@ -36,5 +36,21 @@ struct cmd_results *cmd_sticky(int argc, char **argv) {
36 36
37 container->is_sticky = wants_sticky; 37 container->is_sticky = wants_sticky;
38 38
39 if (wants_sticky) {
40 // move container to focused workspace
41 struct sway_container *output = container_parent(container, C_OUTPUT);
42 struct sway_seat *seat = input_manager_current_seat(input_manager);
43 struct sway_container *focus = seat_get_focus_inactive(seat, output);
44 struct sway_container *focused_workspace = container_parent(focus, C_WORKSPACE);
45 struct sway_container *current_workspace = container_parent(container, C_WORKSPACE);
46 if (current_workspace != focused_workspace) {
47 container_move_to(container, focused_workspace);
48 arrange_windows(focused_workspace);
49 if (!container_reap_empty(current_workspace)) {
50 arrange_windows(current_workspace);
51 }
52 }
53 }
54
39 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 55 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
40} 56}
diff --git a/sway/commands/swap.c b/sway/commands/swap.c
index 4e3a9cce..f881a002 100644
--- a/sway/commands/swap.c
+++ b/sway/commands/swap.c
@@ -50,13 +50,13 @@ struct cmd_results *cmd_swap(int argc, char **argv) {
50 if (strcasecmp(argv[2], "id") == 0) { 50 if (strcasecmp(argv[2], "id") == 0) {
51#ifdef HAVE_XWAYLAND 51#ifdef HAVE_XWAYLAND
52 xcb_window_t id = strtol(value, NULL, 0); 52 xcb_window_t id = strtol(value, NULL, 0);
53 other = container_find(&root_container, test_id, (void *)&id); 53 other = root_find_container(test_id, (void *)&id);
54#endif 54#endif
55 } else if (strcasecmp(argv[2], "con_id") == 0) { 55 } else if (strcasecmp(argv[2], "con_id") == 0) {
56 size_t con_id = atoi(value); 56 size_t con_id = atoi(value);
57 other = container_find(&root_container, test_con_id, (void *)con_id); 57 other = root_find_container(test_con_id, (void *)con_id);
58 } else if (strcasecmp(argv[2], "mark") == 0) { 58 } else if (strcasecmp(argv[2], "mark") == 0) {
59 other = container_find(&root_container, test_mark, (void *)value); 59 other = root_find_container(test_mark, (void *)value);
60 } else { 60 } else {
61 free(value); 61 free(value);
62 return cmd_results_new(CMD_INVALID, "swap", EXPECTED_SYNTAX); 62 return cmd_results_new(CMD_INVALID, "swap", EXPECTED_SYNTAX);
@@ -72,7 +72,7 @@ struct cmd_results *cmd_swap(int argc, char **argv) {
72 || container_has_ancestor(other, current)) { 72 || container_has_ancestor(other, current)) {
73 error = cmd_results_new(CMD_FAILURE, "swap", 73 error = cmd_results_new(CMD_FAILURE, "swap",
74 "Cannot swap ancestor and descendant"); 74 "Cannot swap ancestor and descendant");
75 } else if (current->layout == L_FLOATING || other->layout == L_FLOATING) { 75 } else if (container_is_floating(current) || container_is_floating(other)) {
76 error = cmd_results_new(CMD_FAILURE, "swap", 76 error = cmd_results_new(CMD_FAILURE, "swap",
77 "Swapping with floating containers is not supported"); 77 "Swapping with floating containers is not supported");
78 } 78 }
diff --git a/sway/commands/unmark.c b/sway/commands/unmark.c
index 44ceccee..c183785b 100644
--- a/sway/commands/unmark.c
+++ b/sway/commands/unmark.c
@@ -52,8 +52,7 @@ struct cmd_results *cmd_unmark(int argc, char **argv) {
52 view_find_and_unmark(mark); 52 view_find_and_unmark(mark);
53 } else { 53 } else {
54 // Remove all marks from all views 54 // Remove all marks from all views
55 container_for_each_descendant(&root_container, 55 root_for_each_container(remove_all_marks_iterator, NULL);
56 remove_all_marks_iterator, NULL);
57 } 56 }
58 free(mark); 57 free(mark);
59 58
diff --git a/sway/commands/workspace.c b/sway/commands/workspace.c
index f5558bb4..ceb4cd6e 100644
--- a/sway/commands/workspace.c
+++ b/sway/commands/workspace.c
@@ -1,4 +1,5 @@
1#define _XOPEN_SOURCE 500 1#define _XOPEN_SOURCE 500
2#include <ctype.h>
2#include <string.h> 3#include <string.h>
3#include <strings.h> 4#include <strings.h>
4#include "sway/commands.h" 5#include "sway/commands.h"
@@ -60,9 +61,13 @@ struct cmd_results *cmd_workspace(int argc, char **argv) {
60 struct sway_container *ws = NULL; 61 struct sway_container *ws = NULL;
61 if (strcasecmp(argv[0], "number") == 0) { 62 if (strcasecmp(argv[0], "number") == 0) {
62 if (argc < 2) { 63 if (argc < 2) {
63 cmd_results_new(CMD_INVALID, "workspace", 64 return cmd_results_new(CMD_INVALID, "workspace",
64 "Expected workspace number"); 65 "Expected workspace number");
65 } 66 }
67 if (!isdigit(argv[1][0])) {
68 return cmd_results_new(CMD_INVALID, "workspace",
69 "Invalid workspace number '%s'", argv[1]);
70 }
66 if (!(ws = workspace_by_number(argv[1]))) { 71 if (!(ws = workspace_by_number(argv[1]))) {
67 char *name = join_args(argv + 1, argc - 1); 72 char *name = join_args(argv + 1, argc - 1);
68 ws = workspace_create(NULL, name); 73 ws = workspace_create(NULL, name);
diff --git a/sway/config.c b/sway/config.c
index bd14222a..642abbac 100644
--- a/sway/config.c
+++ b/sway/config.c
@@ -822,18 +822,7 @@ void config_update_font_height(bool recalculate) {
822 size_t prev_max_height = config->font_height; 822 size_t prev_max_height = config->font_height;
823 config->font_height = 0; 823 config->font_height = 0;
824 824
825 container_for_each_descendant(&root_container, 825 root_for_each_container(find_font_height_iterator, &recalculate);
826 find_font_height_iterator, &recalculate);
827
828 // Also consider floating views
829 for (int i = 0; i < root_container.children->length; ++i) {
830 struct sway_container *output = root_container.children->items[i];
831 for (int j = 0; j < output->children->length; ++j) {
832 struct sway_container *ws = output->children->items[j];
833 container_for_each_descendant(ws->sway_workspace->floating,
834 find_font_height_iterator, &recalculate);
835 }
836 }
837 826
838 if (config->font_height != prev_max_height) { 827 if (config->font_height != prev_max_height) {
839 arrange_windows(&root_container); 828 arrange_windows(&root_container);
diff --git a/sway/criteria.c b/sway/criteria.c
index a5df1eef..81c2325a 100644
--- a/sway/criteria.c
+++ b/sway/criteria.c
@@ -167,8 +167,7 @@ static bool criteria_matches_view(struct criteria *criteria,
167 return false; 167 return false;
168 } 168 }
169 list_t *urgent_views = create_list(); 169 list_t *urgent_views = create_list();
170 container_for_each_descendant(&root_container, 170 root_for_each_container(find_urgent_iterator, urgent_views);
171 find_urgent_iterator, urgent_views);
172 list_stable_sort(urgent_views, cmp_urgent); 171 list_stable_sort(urgent_views, cmp_urgent);
173 struct sway_view *target; 172 struct sway_view *target;
174 if (criteria->urgent == 'o') { // oldest 173 if (criteria->urgent == 'o') { // oldest
@@ -228,17 +227,7 @@ list_t *criteria_get_views(struct criteria *criteria) {
228 .criteria = criteria, 227 .criteria = criteria,
229 .matches = matches, 228 .matches = matches,
230 }; 229 };
231 container_for_each_descendant(&root_container, 230 root_for_each_container(criteria_get_views_iterator, &data);
232 criteria_get_views_iterator, &data);
233
234 // Scratchpad items which are hidden are not in the tree.
235 for (int i = 0; i < root_container.sway_root->scratchpad->length; ++i) {
236 struct sway_container *con =
237 root_container.sway_root->scratchpad->items[i];
238 if (!con->parent) {
239 criteria_get_views_iterator(con, &data);
240 }
241 }
242 return matches; 231 return matches;
243} 232}
244 233
diff --git a/sway/debug-tree.c b/sway/debug-tree.c
index f3465afe..ea0826b9 100644
--- a/sway/debug-tree.c
+++ b/sway/debug-tree.c
@@ -3,8 +3,10 @@
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/layout.h"
@@ -12,6 +14,8 @@
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:
@@ -22,8 +26,6 @@ static const char *layout_to_str(enum sway_container_layout layout) {
22 return "L_STACKED"; 26 return "L_STACKED";
23 case L_TABBED: 27 case L_TABBED:
24 return "L_TABBED"; 28 return "L_TABBED";
25 case L_FLOATING:
26 return "L_FLOATING";
27 case L_NONE: 29 case L_NONE:
28 return "L_NONE"; 30 return "L_NONE";
29 } 31 }
@@ -69,10 +71,8 @@ static int draw_container(cairo_t *cairo, struct sway_container *container,
69 return height; 71 return height;
70} 72}
71 73
72bool enable_debug_tree = false;
73
74void update_debug_tree() { 74void update_debug_tree() {
75 if (!enable_debug_tree) { 75 if (!debug.render_tree) {
76 return; 76 return;
77 } 77 }
78 78
diff --git a/sway/desktop/desktop.c b/sway/desktop/desktop.c
index 6575519d..72650397 100644
--- a/sway/desktop/desktop.c
+++ b/sway/desktop/desktop.c
@@ -22,3 +22,21 @@ void desktop_damage_whole_container(struct sway_container *con) {
22 } 22 }
23 } 23 }
24} 24}
25
26void desktop_damage_box(struct wlr_box *box) {
27 for (int i = 0; i < root_container.children->length; ++i) {
28 struct sway_container *cont = root_container.children->items[i];
29 output_damage_box(cont->sway_output, box);
30 }
31}
32
33void desktop_damage_view(struct sway_view *view) {
34 desktop_damage_whole_container(view->swayc);
35 struct wlr_box box = {
36 .x = view->swayc->current.view_x - view->geometry.x,
37 .y = view->swayc->current.view_y - view->geometry.y,
38 .width = view->surface->current.width,
39 .height = view->surface->current.height,
40 };
41 desktop_damage_box(&box);
42}
diff --git a/sway/desktop/output.c b/sway/desktop/output.c
index 66747a3f..3d8bbff5 100644
--- a/sway/desktop/output.c
+++ b/sway/desktop/output.c
@@ -144,15 +144,16 @@ void output_view_for_each_surface(struct sway_output *output,
144 .user_iterator = iterator, 144 .user_iterator = iterator,
145 .user_data = user_data, 145 .user_data = user_data,
146 .output = output, 146 .output = output,
147 .ox = view->swayc->current.view_x - output->swayc->current.swayc_x, 147 .ox = view->swayc->current.view_x - output->swayc->current.swayc_x
148 .oy = view->swayc->current.view_y - output->swayc->current.swayc_y, 148 - view->geometry.x,
149 .oy = view->swayc->current.view_y - output->swayc->current.swayc_y
150 - view->geometry.y,
149 .width = view->swayc->current.view_width, 151 .width = view->swayc->current.view_width,
150 .height = view->swayc->current.view_height, 152 .height = view->swayc->current.view_height,
151 .rotation = 0, // TODO 153 .rotation = 0, // TODO
152 }; 154 };
153 155
154 view_for_each_surface(view, 156 view_for_each_surface(view, output_for_each_surface_iterator, &data);
155 output_for_each_surface_iterator, &data);
156} 157}
157 158
158void output_view_for_each_popup(struct sway_output *output, 159void output_view_for_each_popup(struct sway_output *output,
@@ -162,8 +163,10 @@ void output_view_for_each_popup(struct sway_output *output,
162 .user_iterator = iterator, 163 .user_iterator = iterator,
163 .user_data = user_data, 164 .user_data = user_data,
164 .output = output, 165 .output = output,
165 .ox = view->swayc->current.view_x - output->swayc->current.swayc_x, 166 .ox = view->swayc->current.view_x - output->swayc->current.swayc_x
166 .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,
167 .width = view->swayc->current.view_width, 170 .width = view->swayc->current.view_width,
168 .height = view->swayc->current.view_height, 171 .height = view->swayc->current.view_height,
169 .rotation = 0, // TODO 172 .rotation = 0, // TODO
@@ -303,44 +306,33 @@ struct send_frame_done_data {
303 306
304static void send_frame_done_container_iterator(struct sway_container *con, 307static void send_frame_done_container_iterator(struct sway_container *con,
305 void *_data) { 308 void *_data) {
306 struct send_frame_done_data *data = _data; 309 if (con->type != C_VIEW) {
307 if (!sway_assert(con->type == C_VIEW, "expected a view")) {
308 return; 310 return;
309 } 311 }
310
311 if (!view_is_visible(con->sway_view)) { 312 if (!view_is_visible(con->sway_view)) {
312 return; 313 return;
313 } 314 }
314 315
316 struct send_frame_done_data *data = _data;
315 output_view_for_each_surface(data->output, con->sway_view, 317 output_view_for_each_surface(data->output, con->sway_view,
316 send_frame_done_iterator, data->when); 318 send_frame_done_iterator, data->when);
317} 319}
318 320
319static void send_frame_done_container(struct sway_output *output,
320 struct sway_container *con, struct timespec *when) {
321 struct send_frame_done_data data = {
322 .output = output,
323 .when = when,
324 };
325 container_descendants(con, C_VIEW,
326 send_frame_done_container_iterator, &data);
327}
328
329static void send_frame_done(struct sway_output *output, struct timespec *when) { 321static void send_frame_done(struct sway_output *output, struct timespec *when) {
330 if (output_has_opaque_overlay_layer_surface(output)) { 322 if (output_has_opaque_overlay_layer_surface(output)) {
331 goto send_frame_overlay; 323 goto send_frame_overlay;
332 } 324 }
333 325
326 struct send_frame_done_data data = {
327 .output = output,
328 .when = when,
329 };
334 struct sway_container *workspace = output_get_active_workspace(output); 330 struct sway_container *workspace = output_get_active_workspace(output);
335 if (workspace->current.ws_fullscreen) { 331 if (workspace->current.ws_fullscreen) {
336 if (workspace->current.ws_fullscreen->type == C_VIEW) { 332 send_frame_done_container_iterator(
337 output_view_for_each_surface(output, 333 workspace->current.ws_fullscreen, &data);
338 workspace->current.ws_fullscreen->sway_view, 334 container_for_each_child(workspace->current.ws_fullscreen,
339 send_frame_done_iterator, when); 335 send_frame_done_container_iterator, &data);
340 } else {
341 send_frame_done_container(output, workspace->current.ws_fullscreen,
342 when);
343 }
344#ifdef HAVE_XWAYLAND 336#ifdef HAVE_XWAYLAND
345 send_frame_done_unmanaged(output, 337 send_frame_done_unmanaged(output,
346 &root_container.sway_root->xwayland_unmanaged, when); 338 &root_container.sway_root->xwayland_unmanaged, when);
@@ -351,9 +343,8 @@ static void send_frame_done(struct sway_output *output, struct timespec *when) {
351 send_frame_done_layer(output, 343 send_frame_done_layer(output,
352 &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM], when); 344 &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM], when);
353 345
354 send_frame_done_container(output, workspace, when); 346 workspace_for_each_container(workspace,
355 send_frame_done_container(output, workspace->sway_workspace->floating, 347 send_frame_done_container_iterator, &data);
356 when);
357 348
358#ifdef HAVE_XWAYLAND 349#ifdef HAVE_XWAYLAND
359 send_frame_done_unmanaged(output, 350 send_frame_done_unmanaged(output,
diff --git a/sway/desktop/render.c b/sway/desktop/render.c
index cdac9c72..5cf8abc0 100644
--- a/sway/desktop/render.c
+++ b/sway/desktop/render.c
@@ -193,9 +193,11 @@ static void render_view_toplevels(struct sway_view *view,
193 .alpha = alpha, 193 .alpha = alpha,
194 }; 194 };
195 // Render all toplevels without descending into popups 195 // Render all toplevels without descending into popups
196 output_surface_for_each_surface(output, view->surface, 196 double ox =
197 view->swayc->current.view_x - output->wlr_output->lx, 197 view->swayc->current.view_x - output->wlr_output->lx - view->geometry.x;
198 view->swayc->current.view_y - output->wlr_output->ly, 198 double oy =
199 view->swayc->current.view_y - output->wlr_output->ly - view->geometry.y;
200 output_surface_for_each_surface(output, view->surface, ox, oy,
199 render_surface_iterator, &data); 201 render_surface_iterator, &data);
200} 202}
201 203
@@ -227,8 +229,10 @@ static void render_saved_view(struct sway_view *view,
227 return; 229 return;
228 } 230 }
229 struct wlr_box box = { 231 struct wlr_box box = {
230 .x = view->swayc->current.view_x - output->swayc->current.swayc_x, 232 .x = view->swayc->current.view_x - output->swayc->current.swayc_x -
231 .y = view->swayc->current.view_y - output->swayc->current.swayc_y, 233 view->saved_geometry.x,
234 .y = view->swayc->current.view_y - output->swayc->current.swayc_y -
235 view->saved_geometry.y,
232 .width = view->saved_buffer_width, 236 .width = view->saved_buffer_width,
233 .height = view->saved_buffer_height, 237 .height = view->saved_buffer_height,
234 }; 238 };
@@ -266,7 +270,7 @@ static void render_view(struct sway_output *output, pixman_region32_t *damage,
266 render_view_toplevels(view, output, damage, view->swayc->alpha); 270 render_view_toplevels(view, output, damage, view->swayc->alpha);
267 } 271 }
268 272
269 if (view->using_csd) { 273 if (view->swayc->current.using_csd) {
270 return; 274 return;
271 } 275 }
272 276
@@ -585,7 +589,7 @@ static void render_container_simple(struct sway_output *output,
585 marks_texture = view->marks_unfocused; 589 marks_texture = view->marks_unfocused;
586 } 590 }
587 591
588 if (!view->using_csd) { 592 if (!view->swayc->current.using_csd) {
589 if (state->border == B_NORMAL) { 593 if (state->border == B_NORMAL) {
590 render_titlebar(output, damage, child, state->swayc_x, 594 render_titlebar(output, damage, child, state->swayc_x,
591 state->swayc_y, state->swayc_width, colors, 595 state->swayc_y, state->swayc_width, colors,
@@ -750,8 +754,6 @@ static void render_container(struct sway_output *output,
750 case L_TABBED: 754 case L_TABBED:
751 render_container_tabbed(output, damage, con, parent_focused); 755 render_container_tabbed(output, damage, con, parent_focused);
752 break; 756 break;
753 case L_FLOATING:
754 sway_assert(false, "Didn't expect to see floating here");
755 } 757 }
756} 758}
757 759
@@ -777,7 +779,7 @@ static void render_floating_container(struct sway_output *soutput,
777 marks_texture = view->marks_unfocused; 779 marks_texture = view->marks_unfocused;
778 } 780 }
779 781
780 if (!view->using_csd) { 782 if (!view->swayc->current.using_csd) {
781 if (con->current.border == B_NORMAL) { 783 if (con->current.border == B_NORMAL) {
782 render_titlebar(soutput, damage, con, con->current.swayc_x, 784 render_titlebar(soutput, damage, con, con->current.swayc_x,
783 con->current.swayc_y, con->current.swayc_width, colors, 785 con->current.swayc_y, con->current.swayc_width, colors,
@@ -802,8 +804,7 @@ static void render_floating(struct sway_output *soutput,
802 if (!workspace_is_visible(ws)) { 804 if (!workspace_is_visible(ws)) {
803 continue; 805 continue;
804 } 806 }
805 list_t *floating = 807 list_t *floating = ws->current.ws_floating;
806 ws->current.ws_floating->current.children;
807 for (int k = 0; k < floating->length; ++k) { 808 for (int k = 0; k < floating->length; ++k) {
808 struct sway_container *floater = floating->items[k]; 809 struct sway_container *floater = floating->items[k];
809 render_floating_container(soutput, damage, floater); 810 render_floating_container(soutput, damage, floater);
@@ -812,8 +813,6 @@ static void render_floating(struct sway_output *soutput,
812 } 813 }
813} 814}
814 815
815const char *damage_debug = NULL;
816
817void output_render(struct sway_output *output, struct timespec *when, 816void output_render(struct sway_output *output, struct timespec *when,
818 pixman_region32_t *damage) { 817 pixman_region32_t *damage) {
819 struct wlr_output *wlr_output = output->wlr_output; 818 struct wlr_output *wlr_output = output->wlr_output;
@@ -827,21 +826,17 @@ void output_render(struct sway_output *output, struct timespec *when,
827 826
828 wlr_renderer_begin(renderer, wlr_output->width, wlr_output->height); 827 wlr_renderer_begin(renderer, wlr_output->width, wlr_output->height);
829 828
830 bool damage_whole_before_swap = false;
831 if (!pixman_region32_not_empty(damage)) { 829 if (!pixman_region32_not_empty(damage)) {
832 // Output isn't damaged but needs buffer swap 830 // Output isn't damaged but needs buffer swap
833 goto renderer_end; 831 goto renderer_end;
834 } 832 }
835 833
836 if (damage_debug != NULL) { 834 if (debug.damage == DAMAGE_HIGHLIGHT) {
837 if (strcmp(damage_debug, "highlight") == 0) { 835 wlr_renderer_clear(renderer, (float[]){1, 1, 0, 1});
838 wlr_renderer_clear(renderer, (float[]){1, 1, 0, 1}); 836 } else if (debug.damage == DAMAGE_RERENDER) {
839 damage_whole_before_swap = true; 837 int width, height;
840 } else if (strcmp(damage_debug, "rerender") == 0) { 838 wlr_output_transformed_resolution(wlr_output, &width, &height);
841 int width, height; 839 pixman_region32_union_rect(damage, damage, 0, 0, width, height);
842 wlr_output_transformed_resolution(wlr_output, &width, &height);
843 pixman_region32_union_rect(damage, damage, 0, 0, width, height);
844 }
845 } 840 }
846 841
847 struct sway_container *workspace = output_get_active_workspace(output); 842 struct sway_container *workspace = output_get_active_workspace(output);
@@ -915,12 +910,12 @@ render_overlay:
915 render_drag_icons(output, damage, &root_container.sway_root->drag_icons); 910 render_drag_icons(output, damage, &root_container.sway_root->drag_icons);
916 911
917renderer_end: 912renderer_end:
918 if (root_container.sway_root->debug_tree) { 913 if (debug.render_tree) {
914 wlr_renderer_scissor(renderer, NULL);
919 wlr_render_texture(renderer, root_container.sway_root->debug_tree, 915 wlr_render_texture(renderer, root_container.sway_root->debug_tree,
920 wlr_output->transform_matrix, 0, 0, 1); 916 wlr_output->transform_matrix, 0, 40, 1);
921 } 917 }
922 918 if (debug.damage == DAMAGE_HIGHLIGHT) {
923 if (damage_whole_before_swap || root_container.sway_root->debug_tree) {
924 int width, height; 919 int width, height;
925 wlr_output_transformed_resolution(wlr_output, &width, &height); 920 wlr_output_transformed_resolution(wlr_output, &width, &height);
926 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 c08730ce..f82e5ef2 100644
--- a/sway/desktop/transaction.c
+++ b/sway/desktop/transaction.c
@@ -1,11 +1,13 @@
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>
6#include <time.h> 7#include <time.h>
7#include <wlr/types/wlr_buffer.h> 8#include <wlr/types/wlr_buffer.h>
8#include "sway/debug.h" 9#include "sway/debug.h"
10#include "sway/desktop.h"
9#include "sway/desktop/idle_inhibit_v1.h" 11#include "sway/desktop/idle_inhibit_v1.h"
10#include "sway/desktop/transaction.h" 12#include "sway/desktop/transaction.h"
11#include "sway/output.h" 13#include "sway/output.h"
@@ -15,26 +17,12 @@
15#include "list.h" 17#include "list.h"
16#include "log.h" 18#include "log.h"
17 19
18/**
19 * How long we should wait for views to respond to the configure before giving
20 * up and applying the transaction anyway.
21 */
22int txn_timeout_ms = 200;
23
24/**
25 * If enabled, sway will always wait for the transaction timeout before
26 * applying it, rather than applying it when the views are ready. This allows us
27 * to observe the rendered state while a transaction is in progress.
28 */
29bool txn_debug = false;
30
31struct sway_transaction { 20struct sway_transaction {
32 struct wl_event_source *timer; 21 struct wl_event_source *timer;
33 list_t *instructions; // struct sway_transaction_instruction * 22 list_t *instructions; // struct sway_transaction_instruction *
34 size_t num_waiting; 23 size_t num_waiting;
35 size_t num_configures; 24 size_t num_configures;
36 uint32_t con_ids; // Bitwise XOR of view container IDs 25 uint32_t con_ids; // Bitwise XOR of view container IDs
37 struct timespec create_time;
38 struct timespec commit_time; 26 struct timespec commit_time;
39}; 27};
40 28
@@ -52,9 +40,6 @@ static struct sway_transaction *transaction_create() {
52 return NULL; 40 return NULL;
53 } 41 }
54 transaction->instructions = create_list(); 42 transaction->instructions = create_list();
55 if (server.debug_txn_timings) {
56 clock_gettime(CLOCK_MONOTONIC, &transaction->create_time);
57 }
58 return transaction; 43 return transaction;
59} 44}
60 45
@@ -107,10 +92,12 @@ static void copy_pending_state(struct sway_container *container,
107 state->border_left = view->border_left; 92 state->border_left = view->border_left;
108 state->border_right = view->border_right; 93 state->border_right = view->border_right;
109 state->border_bottom = view->border_bottom; 94 state->border_bottom = view->border_bottom;
95 state->using_csd = view->using_csd;
110 } else if (container->type == C_WORKSPACE) { 96 } else if (container->type == C_WORKSPACE) {
111 state->ws_fullscreen = container->sway_workspace->fullscreen; 97 state->ws_fullscreen = container->sway_workspace->fullscreen;
112 state->ws_floating = container->sway_workspace->floating; 98 state->ws_floating = create_list();
113 state->children = create_list(); 99 state->children = create_list();
100 list_cat(state->ws_floating, container->sway_workspace->floating);
114 list_cat(state->children, container->children); 101 list_cat(state->children, container->children);
115 } else { 102 } else {
116 state->children = create_list(); 103 state->children = create_list();
@@ -147,19 +134,14 @@ static void transaction_add_container(struct sway_transaction *transaction,
147 */ 134 */
148static void transaction_apply(struct sway_transaction *transaction) { 135static void transaction_apply(struct sway_transaction *transaction) {
149 wlr_log(WLR_DEBUG, "Applying transaction %p", transaction); 136 wlr_log(WLR_DEBUG, "Applying transaction %p", transaction);
150 if (server.debug_txn_timings) { 137 if (debug.txn_timings) {
151 struct timespec now; 138 struct timespec now;
152 clock_gettime(CLOCK_MONOTONIC, &now); 139 clock_gettime(CLOCK_MONOTONIC, &now);
153 struct timespec *create = &transaction->create_time;
154 struct timespec *commit = &transaction->commit_time; 140 struct timespec *commit = &transaction->commit_time;
155 float ms_arranging = (commit->tv_sec - create->tv_sec) * 1000 + 141 float ms = (now.tv_sec - commit->tv_sec) * 1000 +
156 (commit->tv_nsec - create->tv_nsec) / 1000000.0;
157 float ms_waiting = (now.tv_sec - commit->tv_sec) * 1000 +
158 (now.tv_nsec - commit->tv_nsec) / 1000000.0; 142 (now.tv_nsec - commit->tv_nsec) / 1000000.0;
159 float ms_total = ms_arranging + ms_waiting; 143 wlr_log(WLR_DEBUG, "Transaction %p: %.1fms waiting "
160 wlr_log(WLR_DEBUG, "Transaction %p: %.1fms arranging, %.1fms waiting, " 144 "(%.1f frames if 60Hz)", transaction, ms, ms / (1000.0f / 60));
161 "%.1fms total (%.1f frames if 60Hz)", transaction,
162 ms_arranging, ms_waiting, ms_total, ms_total / (1000.0f / 60));
163 } 145 }
164 146
165 // Apply the instruction state to the container's current state 147 // Apply the instruction state to the container's current state
@@ -168,25 +150,17 @@ static void transaction_apply(struct sway_transaction *transaction) {
168 transaction->instructions->items[i]; 150 transaction->instructions->items[i];
169 struct sway_container *container = instruction->container; 151 struct sway_container *container = instruction->container;
170 152
171 // Damage the old and new locations 153 // Damage the old location
172 struct wlr_box old_box = { 154 desktop_damage_whole_container(container);
173 .x = container->current.swayc_x, 155 if (container->type == C_VIEW && container->sway_view->saved_buffer) {
174 .y = container->current.swayc_y, 156 struct sway_view *view = container->sway_view;
175 .width = container->current.swayc_width, 157 struct wlr_box box = {
176 .height = container->current.swayc_height, 158 .x = container->current.view_x - view->saved_geometry.x,
177 }; 159 .y = container->current.view_y - view->saved_geometry.y,
178 struct wlr_box new_box = { 160 .width = view->saved_buffer_width,
179 .x = instruction->state.swayc_x, 161 .height = view->saved_buffer_height,
180 .y = instruction->state.swayc_y, 162 };
181 .width = instruction->state.swayc_width, 163 desktop_damage_box(&box);
182 .height = instruction->state.swayc_height,
183 };
184 for (int j = 0; j < root_container.current.children->length; ++j) {
185 struct sway_container *output = root_container.current.children->items[j];
186 if (output->sway_output) {
187 output_damage_box(output->sway_output, &old_box);
188 output_damage_box(output->sway_output, &new_box);
189 }
190 } 164 }
191 165
192 // There are separate children lists for each instruction state, the 166 // There are separate children lists for each instruction state, the
@@ -195,15 +169,35 @@ static void transaction_apply(struct sway_transaction *transaction) {
195 // Any child containers which are being deleted will be cleaned up in 169 // Any child containers which are being deleted will be cleaned up in
196 // transaction_destroy(). 170 // transaction_destroy().
197 list_free(container->current.children); 171 list_free(container->current.children);
172 list_free(container->current.ws_floating);
198 173
199 memcpy(&container->current, &instruction->state, 174 memcpy(&container->current, &instruction->state,
200 sizeof(struct sway_container_state)); 175 sizeof(struct sway_container_state));
201 176
202 if (container->type == C_VIEW && container->sway_view->saved_buffer) { 177 if (container->type == C_VIEW && container->sway_view->saved_buffer) {
203 view_remove_saved_buffer(container->sway_view); 178 if (!container->destroying || container->ntxnrefs == 1) {
179 view_remove_saved_buffer(container->sway_view);
180 }
181 }
182
183 // Damage the new location
184 desktop_damage_whole_container(container);
185 if (container->type == C_VIEW && container->sway_view->surface) {
186 struct sway_view *view = container->sway_view;
187 struct wlr_surface *surface = view->surface;
188 struct wlr_box box = {
189 .x = container->current.view_x - view->geometry.x,
190 .y = container->current.view_y - view->geometry.y,
191 .width = surface->current.width,
192 .height = surface->current.height,
193 };
194 desktop_damage_box(&box);
204 } 195 }
205 196
206 container->instruction = NULL; 197 container->instruction = NULL;
198 if (container->type == C_CONTAINER || container->type == C_VIEW) {
199 container_discover_outputs(container);
200 }
207 } 201 }
208} 202}
209 203
@@ -294,31 +288,38 @@ static void transaction_commit(struct sway_transaction *transaction) {
294 struct timespec when; 288 struct timespec when;
295 wlr_surface_send_frame_done(con->sway_view->surface, &when); 289 wlr_surface_send_frame_done(con->sway_view->surface, &when);
296 } 290 }
297 if (con->type == C_VIEW) { 291 if (con->type == C_VIEW && !con->sway_view->saved_buffer) {
298 view_save_buffer(con->sway_view); 292 view_save_buffer(con->sway_view);
293 memcpy(&con->sway_view->saved_geometry, &con->sway_view->geometry,
294 sizeof(struct wlr_box));
299 } 295 }
300 con->instruction = instruction; 296 con->instruction = instruction;
301 } 297 }
302 transaction->num_configures = transaction->num_waiting; 298 transaction->num_configures = transaction->num_waiting;
303 if (server.debug_txn_timings) { 299 if (debug.txn_timings) {
304 clock_gettime(CLOCK_MONOTONIC, &transaction->commit_time); 300 clock_gettime(CLOCK_MONOTONIC, &transaction->commit_time);
305 } 301 }
302 if (debug.noatomic) {
303 transaction->num_waiting = 0;
304 } else if (debug.txn_wait) {
305 // Force the transaction to time out even if all views are ready.
306 // We do this by inflating the waiting counter.
307 transaction->num_waiting += 1000000;
308 }
306 309
307 if (transaction->num_waiting) { 310 if (transaction->num_waiting) {
308 // Set up a timer which the views must respond within 311 // Set up a timer which the views must respond within
309 transaction->timer = wl_event_loop_add_timer(server.wl_event_loop, 312 transaction->timer = wl_event_loop_add_timer(server.wl_event_loop,
310 handle_timeout, transaction); 313 handle_timeout, transaction);
311 if (transaction->timer) { 314 if (transaction->timer) {
312 wl_event_source_timer_update(transaction->timer, txn_timeout_ms); 315 wl_event_source_timer_update(transaction->timer,
316 server.txn_timeout_ms);
313 } else { 317 } else {
314 wlr_log(WLR_ERROR, "Unable to create transaction timer (%s). " 318 wlr_log(WLR_ERROR, "Unable to create transaction timer (%s). "
315 "Some imperfect frames might be rendered.", 319 "Some imperfect frames might be rendered.",
316 strerror(errno)); 320 strerror(errno));
317 handle_timeout(transaction); 321 transaction->num_waiting = 0;
318 } 322 }
319 } else {
320 wlr_log(WLR_DEBUG,
321 "Transaction %p has nothing to wait for", transaction);
322 } 323 }
323 324
324 // The debug tree shows the pending/live tree. Here is a good place to 325 // The debug tree shows the pending/live tree. Here is a good place to
@@ -331,7 +332,7 @@ static void set_instruction_ready(
331 struct sway_transaction_instruction *instruction) { 332 struct sway_transaction_instruction *instruction) {
332 struct sway_transaction *transaction = instruction->transaction; 333 struct sway_transaction *transaction = instruction->transaction;
333 334
334 if (server.debug_txn_timings) { 335 if (debug.txn_timings) {
335 struct timespec now; 336 struct timespec now;
336 clock_gettime(CLOCK_MONOTONIC, &now); 337 clock_gettime(CLOCK_MONOTONIC, &now);
337 struct timespec *start = &transaction->commit_time; 338 struct timespec *start = &transaction->commit_time;
@@ -342,15 +343,12 @@ static void set_instruction_ready(
342 transaction->num_configures - transaction->num_waiting + 1, 343 transaction->num_configures - transaction->num_waiting + 1,
343 transaction->num_configures, ms, 344 transaction->num_configures, ms,
344 instruction->container->name); 345 instruction->container->name);
345
346 } 346 }
347 347
348 // If the transaction has timed out then its num_waiting will be 0 already. 348 // If the transaction has timed out then its num_waiting will be 0 already.
349 if (transaction->num_waiting > 0 && --transaction->num_waiting == 0) { 349 if (transaction->num_waiting > 0 && --transaction->num_waiting == 0) {
350 if (!txn_debug) { 350 wlr_log(WLR_DEBUG, "Transaction %p is ready", transaction);
351 wlr_log(WLR_DEBUG, "Transaction %p is ready", transaction); 351 wl_event_source_timer_update(transaction->timer, 0);
352 wl_event_source_timer_update(transaction->timer, 0);
353 }
354 } 352 }
355 353
356 instruction->container->instruction = NULL; 354 instruction->container->instruction = NULL;
diff --git a/sway/desktop/xdg_shell.c b/sway/desktop/xdg_shell.c
index 6a7a3f7f..aae129bd 100644
--- a/sway/desktop/xdg_shell.c
+++ b/sway/desktop/xdg_shell.c
@@ -7,6 +7,7 @@
7#include <wlr/util/edges.h> 7#include <wlr/util/edges.h>
8#include "log.h" 8#include "log.h"
9#include "sway/decoration.h" 9#include "sway/decoration.h"
10#include "sway/desktop.h"
10#include "sway/input/input-manager.h" 11#include "sway/input/input-manager.h"
11#include "sway/input/seat.h" 12#include "sway/input/seat.h"
12#include "sway/server.h" 13#include "sway/server.h"
@@ -107,7 +108,8 @@ static void get_constraints(struct sway_view *view, double *min_width,
107 *max_height = state->max_height > 0 ? state->max_height : DBL_MAX; 108 *max_height = state->max_height > 0 ? state->max_height : DBL_MAX;
108} 109}
109 110
110static const char *get_string_prop(struct sway_view *view, enum sway_view_prop prop) { 111static const char *get_string_prop(struct sway_view *view,
112 enum sway_view_prop prop) {
111 if (xdg_shell_view_from_view(view) == NULL) { 113 if (xdg_shell_view_from_view(view) == NULL) {
112 return NULL; 114 return NULL;
113 } 115 }
@@ -255,8 +257,24 @@ static void handle_commit(struct wl_listener *listener, void *data) {
255 } 257 }
256 258
257 if (view->swayc->instruction) { 259 if (view->swayc->instruction) {
260 wlr_xdg_surface_get_geometry(xdg_surface, &view->geometry);
258 transaction_notify_view_ready_by_serial(view, 261 transaction_notify_view_ready_by_serial(view,
259 xdg_surface->configure_serial); 262 xdg_surface->configure_serial);
263 } else {
264 struct wlr_box new_geo;
265 wlr_xdg_surface_get_geometry(xdg_surface, &new_geo);
266
267 if ((new_geo.width != view->width || new_geo.height != view->height) &&
268 container_is_floating(view->swayc)) {
269 // A floating view has unexpectedly sent a new size
270 desktop_damage_view(view);
271 view_update_size(view, new_geo.width, new_geo.height);
272 memcpy(&view->geometry, &new_geo, sizeof(struct wlr_box));
273 desktop_damage_view(view);
274 transaction_commit_dirty();
275 } else {
276 memcpy(&view->geometry, &new_geo, sizeof(struct wlr_box));
277 }
260 } 278 }
261 279
262 view_damage_from(view); 280 view_damage_from(view);
diff --git a/sway/desktop/xdg_shell_v6.c b/sway/desktop/xdg_shell_v6.c
index 5b3c7b2b..277c53a3 100644
--- a/sway/desktop/xdg_shell_v6.c
+++ b/sway/desktop/xdg_shell_v6.c
@@ -6,6 +6,7 @@
6#include <wlr/types/wlr_xdg_shell_v6.h> 6#include <wlr/types/wlr_xdg_shell_v6.h>
7#include "log.h" 7#include "log.h"
8#include "sway/decoration.h" 8#include "sway/decoration.h"
9#include "sway/desktop.h"
9#include "sway/input/input-manager.h" 10#include "sway/input/input-manager.h"
10#include "sway/input/seat.h" 11#include "sway/input/seat.h"
11#include "sway/server.h" 12#include "sway/server.h"
@@ -106,7 +107,8 @@ static void get_constraints(struct sway_view *view, double *min_width,
106 *max_height = state->max_height > 0 ? state->max_height : DBL_MAX; 107 *max_height = state->max_height > 0 ? state->max_height : DBL_MAX;
107} 108}
108 109
109static const char *get_string_prop(struct sway_view *view, enum sway_view_prop prop) { 110static const char *get_string_prop(struct sway_view *view,
111 enum sway_view_prop prop) {
110 if (xdg_shell_v6_view_from_view(view) == NULL) { 112 if (xdg_shell_v6_view_from_view(view) == NULL) {
111 return NULL; 113 return NULL;
112 } 114 }
@@ -250,9 +252,26 @@ static void handle_commit(struct wl_listener *listener, void *data) {
250 if (!view->swayc) { 252 if (!view->swayc) {
251 return; 253 return;
252 } 254 }
255
253 if (view->swayc->instruction) { 256 if (view->swayc->instruction) {
257 wlr_xdg_surface_v6_get_geometry(xdg_surface_v6, &view->geometry);
254 transaction_notify_view_ready_by_serial(view, 258 transaction_notify_view_ready_by_serial(view,
255 xdg_surface_v6->configure_serial); 259 xdg_surface_v6->configure_serial);
260 } else {
261 struct wlr_box new_geo;
262 wlr_xdg_surface_v6_get_geometry(xdg_surface_v6, &new_geo);
263
264 if ((new_geo.width != view->width || new_geo.height != view->height) &&
265 container_is_floating(view->swayc)) {
266 // A floating view has unexpectedly sent a new size
267 desktop_damage_view(view);
268 view_update_size(view, new_geo.width, new_geo.height);
269 memcpy(&view->geometry, &new_geo, sizeof(struct wlr_box));
270 desktop_damage_view(view);
271 transaction_commit_dirty();
272 } else {
273 memcpy(&view->geometry, &new_geo, sizeof(struct wlr_box));
274 }
256 } 275 }
257 276
258 view_damage_from(view); 277 view_damage_from(view);
diff --git a/sway/desktop/xwayland.c b/sway/desktop/xwayland.c
index 14f59b9c..ce7235e4 100644
--- a/sway/desktop/xwayland.c
+++ b/sway/desktop/xwayland.c
@@ -277,18 +277,44 @@ static const struct sway_view_impl view_impl = {
277 .destroy = destroy, 277 .destroy = destroy,
278}; 278};
279 279
280static void get_geometry(struct sway_view *view, struct wlr_box *box) {
281 box->x = box->y = 0;
282 if (view->surface) {
283 box->width = view->surface->current.width;
284 box->height = view->surface->current.height;
285 } else {
286 box->width = 0;
287 box->height = 0;
288 }
289}
290
280static void handle_commit(struct wl_listener *listener, void *data) { 291static void handle_commit(struct wl_listener *listener, void *data) {
281 struct sway_xwayland_view *xwayland_view = 292 struct sway_xwayland_view *xwayland_view =
282 wl_container_of(listener, xwayland_view, commit); 293 wl_container_of(listener, xwayland_view, commit);
283 struct sway_view *view = &xwayland_view->view; 294 struct sway_view *view = &xwayland_view->view;
284 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface; 295 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface;
285 struct wlr_surface_state *surface_state = &xsurface->surface->current; 296 struct wlr_surface_state *state = &xsurface->surface->current;
286 297
287 if (view->swayc->instruction) { 298 if (view->swayc->instruction) {
299 get_geometry(view, &view->geometry);
288 transaction_notify_view_ready_by_size(view, 300 transaction_notify_view_ready_by_size(view,
289 surface_state->width, surface_state->height); 301 state->width, state->height);
290 } else if (container_is_floating(view->swayc)) { 302 } else {
291 view_update_size(view, surface_state->width, surface_state->height); 303 struct wlr_box new_geo;
304 get_geometry(view, &new_geo);
305
306 if ((new_geo.width != view->width || new_geo.height != view->height) &&
307 container_is_floating(view->swayc)) {
308 // A floating view has unexpectedly sent a new size
309 // eg. The Firefox "Save As" dialog when downloading a file
310 desktop_damage_view(view);
311 view_update_size(view, new_geo.width, new_geo.height);
312 memcpy(&view->geometry, &new_geo, sizeof(struct wlr_box));
313 desktop_damage_view(view);
314 transaction_commit_dirty();
315 } else {
316 memcpy(&view->geometry, &new_geo, sizeof(struct wlr_box));
317 }
292 } 318 }
293 319
294 view_damage_from(view); 320 view_damage_from(view);
diff --git a/sway/input/cursor.c b/sway/input/cursor.c
index 3b70b471..ba5e0400 100644
--- a/sway/input/cursor.c
+++ b/sway/input/cursor.c
@@ -215,6 +215,19 @@ static enum wlr_edges find_resize_edge(struct sway_container *cont,
215 return edge; 215 return edge;
216} 216}
217 217
218static void handle_down_motion(struct sway_seat *seat,
219 struct sway_cursor *cursor, uint32_t time_msec) {
220 struct sway_container *con = seat->op_container;
221 if (seat_is_input_allowed(seat, con->sway_view->surface)) {
222 double moved_x = cursor->cursor->x - seat->op_ref_lx;
223 double moved_y = cursor->cursor->y - seat->op_ref_ly;
224 double sx = seat->op_ref_con_lx + moved_x;
225 double sy = seat->op_ref_con_ly + moved_y;
226 wlr_seat_pointer_notify_motion(seat->wlr_seat, time_msec, sx, sy);
227 }
228 seat->op_moved = true;
229}
230
218static void handle_move_motion(struct sway_seat *seat, 231static void handle_move_motion(struct sway_seat *seat,
219 struct sway_cursor *cursor) { 232 struct sway_cursor *cursor) {
220 struct sway_container *con = seat->op_container; 233 struct sway_container *con = seat->op_container;
@@ -397,6 +410,9 @@ void cursor_send_pointer_motion(struct sway_cursor *cursor, uint32_t time_msec,
397 410
398 if (seat->operation != OP_NONE) { 411 if (seat->operation != OP_NONE) {
399 switch (seat->operation) { 412 switch (seat->operation) {
413 case OP_DOWN:
414 handle_down_motion(seat, cursor, time_msec);
415 break;
400 case OP_MOVE: 416 case OP_MOVE:
401 handle_move_motion(seat, cursor); 417 handle_move_motion(seat, cursor);
402 break; 418 break;
@@ -708,7 +724,7 @@ void dispatch_cursor_button(struct sway_cursor *cursor,
708 uint32_t btn_move = config->floating_mod_inverse ? BTN_RIGHT : BTN_LEFT; 724 uint32_t btn_move = config->floating_mod_inverse ? BTN_RIGHT : BTN_LEFT;
709 if (button == btn_move && state == WLR_BUTTON_PRESSED && 725 if (button == btn_move && state == WLR_BUTTON_PRESSED &&
710 (mod_pressed || on_titlebar)) { 726 (mod_pressed || on_titlebar)) {
711 while (cont->parent->layout != L_FLOATING) { 727 while (cont->parent->type != C_WORKSPACE) {
712 cont = cont->parent; 728 cont = cont->parent;
713 } 729 }
714 seat_begin_move(seat, cont, button); 730 seat_begin_move(seat, cont, button);
@@ -726,13 +742,13 @@ void dispatch_cursor_button(struct sway_cursor *cursor,
726 } 742 }
727 743
728 // Via mod+click 744 // Via mod+click
729 struct sway_container *floater = cont;
730 while (floater->parent->layout != L_FLOATING) {
731 floater = floater->parent;
732 }
733 uint32_t btn_resize = config->floating_mod_inverse ? 745 uint32_t btn_resize = config->floating_mod_inverse ?
734 BTN_LEFT : BTN_RIGHT; 746 BTN_LEFT : BTN_RIGHT;
735 if (button == btn_resize) { 747 if (mod_pressed && button == btn_resize) {
748 struct sway_container *floater = cont;
749 while (floater->parent->type != C_WORKSPACE) {
750 floater = floater->parent;
751 }
736 edge = 0; 752 edge = 0;
737 edge |= cursor->cursor->x > floater->x + floater->width / 2 ? 753 edge |= cursor->cursor->x > floater->x + floater->width / 2 ?
738 WLR_EDGE_RIGHT : WLR_EDGE_LEFT; 754 WLR_EDGE_RIGHT : WLR_EDGE_LEFT;
@@ -743,6 +759,14 @@ void dispatch_cursor_button(struct sway_cursor *cursor,
743 } 759 }
744 } 760 }
745 761
762 // Handle mousedown on a container surface
763 if (surface && cont && state == WLR_BUTTON_PRESSED) {
764 seat_set_focus(seat, cont);
765 seat_pointer_notify_button(seat, time_msec, button, state);
766 seat_begin_down(seat, cont, button, sx, sy);
767 return;
768 }
769
746 // Handle clicking a container surface 770 // Handle clicking a container surface
747 if (cont) { 771 if (cont) {
748 seat_set_focus(seat, cont); 772 seat_set_focus(seat, cont);
diff --git a/sway/input/seat.c b/sway/input/seat.c
index 57cc65f6..4077a8dd 100644
--- a/sway/input/seat.c
+++ b/sway/input/seat.c
@@ -124,42 +124,6 @@ static void seat_send_focus(struct sway_container *con,
124 } 124 }
125} 125}
126 126
127static struct sway_container *seat_get_focus_by_type(struct sway_seat *seat,
128 struct sway_container *container, enum sway_container_type type,
129 bool only_tiling) {
130 if (container->type == C_VIEW) {
131 return container;
132 }
133
134 struct sway_container *floating =
135 container->type == C_WORKSPACE && !only_tiling ?
136 container->sway_workspace->floating : NULL;
137 if (container->children->length == 0 &&
138 (!floating || floating->children->length == 0)) {
139 return container;
140 }
141
142 struct sway_seat_container *current = NULL;
143 wl_list_for_each(current, &seat->focus_stack, link) {
144 if (current->container->type != type && type != C_TYPES) {
145 continue;
146 }
147
148 if (container_has_ancestor(current->container, container)) {
149 if (only_tiling &&
150 container_is_floating_or_child(current->container)) {
151 continue;
152 }
153 return current->container;
154 }
155 if (floating && container_has_ancestor(current->container, floating)) {
156 return current->container;
157 }
158 }
159
160 return NULL;
161}
162
163void seat_focus_inactive_children_for_each(struct sway_seat *seat, 127void seat_focus_inactive_children_for_each(struct sway_seat *seat,
164 struct sway_container *container, 128 struct sway_container *container,
165 void (*f)(struct sway_container *container, void *data), void *data) { 129 void (*f)(struct sway_container *container, void *data), void *data) {
@@ -175,8 +139,18 @@ void seat_focus_inactive_children_for_each(struct sway_seat *seat,
175} 139}
176 140
177struct sway_container *seat_get_focus_inactive_view(struct sway_seat *seat, 141struct sway_container *seat_get_focus_inactive_view(struct sway_seat *seat,
178 struct sway_container *container) { 142 struct sway_container *ancestor) {
179 return seat_get_focus_by_type(seat, container, C_VIEW, false); 143 if (ancestor->type == C_VIEW) {
144 return ancestor;
145 }
146 struct sway_seat_container *current;
147 wl_list_for_each(current, &seat->focus_stack, link) {
148 struct sway_container *con = current->container;
149 if (con->type == C_VIEW && container_has_ancestor(con, ancestor)) {
150 return con;
151 }
152 }
153 return NULL;
180} 154}
181 155
182static void handle_seat_container_destroy(struct wl_listener *listener, 156static void handle_seat_container_destroy(struct wl_listener *listener,
@@ -198,7 +172,7 @@ static void handle_seat_container_destroy(struct wl_listener *listener,
198 if (set_focus) { 172 if (set_focus) {
199 struct sway_container *next_focus = NULL; 173 struct sway_container *next_focus = NULL;
200 while (next_focus == NULL) { 174 while (next_focus == NULL) {
201 next_focus = seat_get_focus_by_type(seat, parent, C_VIEW, false); 175 next_focus = seat_get_focus_inactive_view(seat, parent);
202 176
203 if (next_focus == NULL && parent->type == C_WORKSPACE) { 177 if (next_focus == NULL && parent->type == C_WORKSPACE) {
204 next_focus = parent; 178 next_focus = parent;
@@ -339,9 +313,6 @@ static void handle_new_drag_icon(struct wl_listener *listener, void *data) {
339 313
340static void collect_focus_iter(struct sway_container *con, void *data) { 314static void collect_focus_iter(struct sway_container *con, void *data) {
341 struct sway_seat *seat = data; 315 struct sway_seat *seat = data;
342 if (con->type > C_WORKSPACE) {
343 return;
344 }
345 struct sway_seat_container *seat_con = 316 struct sway_seat_container *seat_con =
346 seat_container_from_container(seat, con); 317 seat_container_from_container(seat, con);
347 if (!seat_con) { 318 if (!seat_con) {
@@ -375,7 +346,8 @@ struct sway_seat *seat_create(struct sway_input_manager *input,
375 // init the focus stack 346 // init the focus stack
376 wl_list_init(&seat->focus_stack); 347 wl_list_init(&seat->focus_stack);
377 348
378 container_for_each_descendant(&root_container, collect_focus_iter, seat); 349 root_for_each_workspace(collect_focus_iter, seat);
350 root_for_each_container(collect_focus_iter, seat);
379 351
380 wl_signal_add(&root_container.sway_root->events.new_container, 352 wl_signal_add(&root_container.sway_root->events.new_container,
381 &seat->new_container); 353 &seat->new_container);
@@ -653,8 +625,7 @@ void seat_set_focus_warp(struct sway_seat *seat,
653 // find new output's old workspace, which might have to be removed if empty 625 // find new output's old workspace, which might have to be removed if empty
654 struct sway_container *new_output_last_ws = NULL; 626 struct sway_container *new_output_last_ws = NULL;
655 if (last_output && new_output && last_output != new_output) { 627 if (last_output && new_output && last_output != new_output) {
656 new_output_last_ws = 628 new_output_last_ws = seat_get_active_child(seat, new_output);
657 seat_get_focus_by_type(seat, new_output, C_WORKSPACE, false);
658 } 629 }
659 630
660 if (container && container->parent) { 631 if (container && container->parent) {
@@ -717,7 +688,8 @@ void seat_set_focus_warp(struct sway_seat *seat,
717 // If we've focused a floating container, bring it to the front. 688 // If we've focused a floating container, bring it to the front.
718 // We do this by putting it at the end of the floating list. 689 // We do this by putting it at the end of the floating list.
719 if (container && container_is_floating(container)) { 690 if (container && container_is_floating(container)) {
720 list_move_to_end(container->parent->children, container); 691 list_move_to_end(
692 container->parent->sway_workspace->floating, container);
721 } 693 }
722 694
723 // clean up unfocused empty workspace on new output 695 // clean up unfocused empty workspace on new output
@@ -877,22 +849,66 @@ void seat_set_exclusive_client(struct sway_seat *seat,
877} 849}
878 850
879struct sway_container *seat_get_focus_inactive(struct sway_seat *seat, 851struct sway_container *seat_get_focus_inactive(struct sway_seat *seat,
880 struct sway_container *container) { 852 struct sway_container *con) {
881 return seat_get_focus_by_type(seat, container, C_TYPES, false); 853 if (con->type == C_WORKSPACE && !con->children->length &&
854 !con->sway_workspace->floating->length) {
855 return con;
856 }
857 if (con->type == C_VIEW) {
858 return con;
859 }
860 struct sway_seat_container *current;
861 wl_list_for_each(current, &seat->focus_stack, link) {
862 if (container_has_ancestor(current->container, con)) {
863 return current->container;
864 }
865 }
866 return NULL;
882} 867}
883 868
884struct sway_container *seat_get_focus_inactive_tiling(struct sway_seat *seat, 869struct sway_container *seat_get_focus_inactive_tiling(struct sway_seat *seat,
885 struct sway_container *container) { 870 struct sway_container *ancestor) {
886 return seat_get_focus_by_type(seat, container, C_TYPES, true); 871 if (ancestor->type == C_WORKSPACE && !ancestor->children->length) {
872 return ancestor;
873 }
874 struct sway_seat_container *current;
875 wl_list_for_each(current, &seat->focus_stack, link) {
876 struct sway_container *con = current->container;
877 if (!container_is_floating_or_child(con) &&
878 container_has_ancestor(current->container, ancestor)) {
879 return con;
880 }
881 }
882 return NULL;
883}
884
885struct sway_container *seat_get_focus_inactive_floating(struct sway_seat *seat,
886 struct sway_container *ancestor) {
887 if (ancestor->type == C_WORKSPACE &&
888 !ancestor->sway_workspace->floating->length) {
889 return NULL;
890 }
891 struct sway_seat_container *current;
892 wl_list_for_each(current, &seat->focus_stack, link) {
893 struct sway_container *con = current->container;
894 if (container_is_floating_or_child(con) &&
895 container_has_ancestor(current->container, ancestor)) {
896 return con;
897 }
898 }
899 return NULL;
887} 900}
888 901
889struct sway_container *seat_get_active_child(struct sway_seat *seat, 902struct sway_container *seat_get_active_child(struct sway_seat *seat,
890 struct sway_container *container) { 903 struct sway_container *parent) {
891 struct sway_seat_container *current = NULL; 904 if (parent->type == C_VIEW) {
905 return parent;
906 }
907 struct sway_seat_container *current;
892 wl_list_for_each(current, &seat->focus_stack, link) { 908 wl_list_for_each(current, &seat->focus_stack, link) {
893 if (current->container->parent == container && 909 struct sway_container *con = current->container;
894 current->container->layout != L_FLOATING) { 910 if (con->parent == parent) {
895 return current->container; 911 return con;
896 } 912 }
897 } 913 }
898 return NULL; 914 return NULL;
@@ -902,7 +918,9 @@ struct sway_container *seat_get_focus(struct sway_seat *seat) {
902 if (!seat->has_focus) { 918 if (!seat->has_focus) {
903 return NULL; 919 return NULL;
904 } 920 }
905 return seat_get_focus_inactive(seat, &root_container); 921 struct sway_seat_container *current =
922 wl_container_of(seat->focus_stack.next, current, link);
923 return current->container;
906} 924}
907 925
908void seat_apply_config(struct sway_seat *seat, 926void seat_apply_config(struct sway_seat *seat,
@@ -930,6 +948,18 @@ struct seat_config *seat_get_config(struct sway_seat *seat) {
930 return NULL; 948 return NULL;
931} 949}
932 950
951void seat_begin_down(struct sway_seat *seat, struct sway_container *con,
952 uint32_t button, double sx, double sy) {
953 seat->operation = OP_DOWN;
954 seat->op_container = con;
955 seat->op_button = button;
956 seat->op_ref_lx = seat->cursor->cursor->x;
957 seat->op_ref_ly = seat->cursor->cursor->y;
958 seat->op_ref_con_lx = sx;
959 seat->op_ref_con_ly = sy;
960 seat->op_moved = false;
961}
962
933void seat_begin_move(struct sway_seat *seat, struct sway_container *con, 963void seat_begin_move(struct sway_seat *seat, struct sway_container *con,
934 uint32_t button) { 964 uint32_t button) {
935 if (!seat->cursor) { 965 if (!seat->cursor) {
@@ -954,7 +984,7 @@ void seat_begin_resize_floating(struct sway_seat *seat,
954 seat->op_resize_preserve_ratio = keyboard && 984 seat->op_resize_preserve_ratio = keyboard &&
955 (wlr_keyboard_get_modifiers(keyboard) & WLR_MODIFIER_SHIFT); 985 (wlr_keyboard_get_modifiers(keyboard) & WLR_MODIFIER_SHIFT);
956 seat->op_resize_edge = edge == WLR_EDGE_NONE ? 986 seat->op_resize_edge = edge == WLR_EDGE_NONE ?
957 RESIZE_EDGE_BOTTOM | RESIZE_EDGE_RIGHT : edge; 987 WLR_EDGE_BOTTOM | WLR_EDGE_RIGHT : edge;
958 seat->op_button = button; 988 seat->op_button = button;
959 seat->op_ref_lx = seat->cursor->cursor->x; 989 seat->op_ref_lx = seat->cursor->cursor->x;
960 seat->op_ref_ly = seat->cursor->cursor->y; 990 seat->op_ref_ly = seat->cursor->cursor->y;
@@ -983,6 +1013,7 @@ void seat_begin_resize_tiling(struct sway_seat *seat,
983} 1013}
984 1014
985void seat_end_mouse_operation(struct sway_seat *seat) { 1015void seat_end_mouse_operation(struct sway_seat *seat) {
1016 enum sway_seat_operation operation = seat->operation;
986 if (seat->operation == OP_MOVE) { 1017 if (seat->operation == OP_MOVE) {
987 // We "move" the container to its own location so it discovers its 1018 // We "move" the container to its own location so it discovers its
988 // output again. 1019 // output again.
@@ -991,7 +1022,19 @@ void seat_end_mouse_operation(struct sway_seat *seat) {
991 } 1022 }
992 seat->operation = OP_NONE; 1023 seat->operation = OP_NONE;
993 seat->op_container = NULL; 1024 seat->op_container = NULL;
994 cursor_set_image(seat->cursor, "left_ptr", NULL); 1025 if (operation == OP_DOWN) {
1026 // Set the cursor's previous coords to the x/y at the start of the
1027 // operation, so the container change will be detected if using
1028 // focus_follows_mouse and the cursor moved off the original container
1029 // during the operation.
1030 seat->cursor->previous.x = seat->op_ref_lx;
1031 seat->cursor->previous.y = seat->op_ref_ly;
1032 if (seat->op_moved) {
1033 cursor_send_pointer_motion(seat->cursor, 0, true);
1034 }
1035 } else {
1036 cursor_set_image(seat->cursor, "left_ptr", NULL);
1037 }
995} 1038}
996 1039
997void seat_pointer_notify_button(struct sway_seat *seat, uint32_t time_msec, 1040void seat_pointer_notify_button(struct sway_seat *seat, uint32_t time_msec,
diff --git a/sway/ipc-json.c b/sway/ipc-json.c
index 4c2bcc98..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"
@@ -23,8 +24,6 @@ static const char *ipc_json_layout_description(enum sway_container_layout l) {
23 return "tabbed"; 24 return "tabbed";
24 case L_STACKED: 25 case L_STACKED:
25 return "stacked"; 26 return "stacked";
26 case L_FLOATING:
27 return "floating";
28 case L_NONE: 27 case L_NONE:
29 break; 28 break;
30 } 29 }
@@ -180,10 +179,11 @@ static void ipc_json_describe_workspace(struct sway_container *workspace,
180 179
181 // Floating 180 // Floating
182 json_object *floating_array = json_object_new_array(); 181 json_object *floating_array = json_object_new_array();
183 struct sway_container *floating = workspace->sway_workspace->floating; 182 list_t *floating = workspace->sway_workspace->floating;
184 for (int i = 0; i < floating->children->length; ++i) { 183 for (int i = 0; i < floating->length; ++i) {
185 struct sway_container *floater = floating->children->items[i]; 184 struct sway_container *floater = floating->items[i];
186 json_object_array_add(floating_array, ipc_json_describe_container_recursive(floater)); 185 json_object_array_add(floating_array,
186 ipc_json_describe_container_recursive(floater));
187 } 187 }
188 json_object_object_add(object, "floating_nodes", floating_array); 188 json_object_object_add(object, "floating_nodes", floating_array);
189} 189}
@@ -193,6 +193,16 @@ static void ipc_json_describe_view(struct sway_container *c, json_object *object
193 c->name ? json_object_new_string(c->name) : NULL); 193 c->name ? json_object_new_string(c->name) : NULL);
194 json_object_object_add(object, "type", json_object_new_string("con")); 194 json_object_object_add(object, "type", json_object_new_string("con"));
195 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
196 if (c->parent) { 206 if (c->parent) {
197 json_object_object_add(object, "layout", 207 json_object_object_add(object, "layout",
198 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 dad1f310..34e940ad 100644
--- a/sway/ipc-server.c
+++ b/sway/ipc-server.c
@@ -522,7 +522,7 @@ void ipc_client_disconnect(struct ipc_client *client) {
522 522
523static void ipc_get_workspaces_callback(struct sway_container *workspace, 523static void ipc_get_workspaces_callback(struct sway_container *workspace,
524 void *data) { 524 void *data) {
525 if (workspace->type != C_WORKSPACE) { 525 if (!sway_assert(workspace->type == C_WORKSPACE, "Expected a workspace")) {
526 return; 526 return;
527 } 527 }
528 json_object *workspace_json = ipc_json_describe_container(workspace); 528 json_object *workspace_json = ipc_json_describe_container(workspace);
@@ -631,8 +631,7 @@ void ipc_client_handle_command(struct ipc_client *client) {
631 case IPC_GET_WORKSPACES: 631 case IPC_GET_WORKSPACES:
632 { 632 {
633 json_object *workspaces = json_object_new_array(); 633 json_object *workspaces = json_object_new_array();
634 container_for_each_descendant(&root_container, 634 root_for_each_workspace(ipc_get_workspaces_callback, workspaces);
635 ipc_get_workspaces_callback, workspaces);
636 const char *json_string = json_object_to_json_string(workspaces); 635 const char *json_string = json_object_to_json_string(workspaces);
637 client_valid = 636 client_valid =
638 ipc_send_reply(client, json_string, (uint32_t)strlen(json_string)); 637 ipc_send_reply(client, json_string, (uint32_t)strlen(json_string));
@@ -729,8 +728,7 @@ void ipc_client_handle_command(struct ipc_client *client) {
729 case IPC_GET_MARKS: 728 case IPC_GET_MARKS:
730 { 729 {
731 json_object *marks = json_object_new_array(); 730 json_object *marks = json_object_new_array();
732 container_descendants(&root_container, C_VIEW, ipc_get_marks_callback, 731 root_for_each_container(ipc_get_marks_callback, marks);
733 marks);
734 const char *json_string = json_object_to_json_string(marks); 732 const char *json_string = json_object_to_json_string(marks);
735 client_valid = 733 client_valid =
736 ipc_send_reply(client, json_string, (uint32_t)strlen(json_string)); 734 ipc_send_reply(client, json_string, (uint32_t)strlen(json_string));
diff --git a/sway/main.c b/sway/main.c
index 54f48340..3ba4ba75 100644
--- a/sway/main.c
+++ b/sway/main.c
@@ -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 2a457270..676422d0 100644
--- a/sway/meson.build
+++ b/sway/meson.build
@@ -64,6 +64,7 @@ sway_sources = files(
64 'commands/mouse_warping.c', 64 'commands/mouse_warping.c',
65 'commands/move.c', 65 'commands/move.c',
66 'commands/no_focus.c', 66 'commands/no_focus.c',
67 'commands/nop.c',
67 'commands/output.c', 68 'commands/output.c',
68 'commands/reload.c', 69 'commands/reload.c',
69 'commands/rename.c', 70 'commands/rename.c',
diff --git a/sway/server.c b/sway/server.c
index e8dc63be..00acaa01 100644
--- a/sway/server.c
+++ b/sway/server.c
@@ -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 70b74a45..83188067 100644
--- a/sway/sway.5.scd
+++ b/sway/sway.5.scd
@@ -51,7 +51,7 @@ The following commands may only be used in the configuration file.
51 *wordexp*(3) for details). The same include file can only be included once; 51 *wordexp*(3) for details). The same include file can only be included once;
52 subsequent attempts will be ignored. 52 subsequent attempts will be ignored.
53 53
54*set* <name> <value> 54*set* $<name> <value>
55 Sets variable $_name_ to _value_. You can use the new variable in the 55 Sets variable $_name_ to _value_. You can use the new variable in the
56 arguments of future commands. 56 arguments of future commands.
57 57
@@ -132,7 +132,7 @@ They are expected to be used with *bindsym* or at runtime through *swaymsg*(1).
132 If unspecified, the default is 10 pixels. Pixels are ignored when moving 132 If unspecified, the default is 10 pixels. Pixels are ignored when moving
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.
137 137
138*move* [absolute] position center|mouse 138*move* [absolute] position center|mouse
@@ -154,7 +154,7 @@ They are expected to be used with *bindsym* or at runtime through *swaymsg*(1).
154 Moves the focused container to the previous or next workspace on this 154 Moves the focused container to the previous or next workspace on this
155 output, wrapping around if already at the first or last workspace. 155 output, wrapping around if already at the first or last workspace.
156 156
157*move* container|window [to] workspace back_and_forth 157*move* container|window [to] workspace back\_and\_forth
158 Moves the focused container to previously focused workspace. 158 Moves the focused container to previously focused workspace.
159 159
160*move* container|window|workspace [to] output <name> 160*move* container|window|workspace [to] output <name>
@@ -167,6 +167,10 @@ They are expected to be used with *bindsym* or at runtime through *swaymsg*(1).
167*move* [to] scratchpad 167*move* [to] scratchpad
168 Moves the focused window to the scratchpad. 168 Moves the focused window to the scratchpad.
169 169
170*nop* <comment>
171 A no operation command that can be used to override default behaviour. The
172 optional comment argument is ignored, but logged for debugging purposes.
173
170*reload* 174*reload*
171 Reloads the sway config file and applies any changes. 175 Reloads the sway config file and applies any changes.
172 176
@@ -215,13 +219,20 @@ They are expected to be used with *bindsym* or at runtime through *swaymsg*(1).
215The following commands may be used either in the configuration file or at 219The following commands may be used either in the configuration file or at
216runtime. 220runtime.
217 221
218*assign* <criteria> [→] <workspace> 222*assign* <criteria> [→] [workspace] [number] <workspace>
219 Assigns views matching _criteria_ (see *CRITERIA* for details) to 223 Assigns views matching _criteria_ (see *CRITERIA* for details) to
220 _workspace_. The → (U+2192) is optional and cosmetic. This command is 224 _workspace_. The → (U+2192) is optional and cosmetic. This command is
221 equivalent to: 225 equivalent to:
222 226
223 for\_window <criteria> move container to workspace <workspace> 227 for\_window <criteria> move container to workspace <workspace>
224 228
229*assign* <criteria> [→] output left|right|up|down|<name>
230 Assigns views matching _criteria_ (see *CRITERIA* for details) to the
231 specified output. The → (U+2192) is optional and cosmetic. This command is
232 equivalent to:
233
234 for\_window <criteria> move container to output <output>
235
225*bindsym* [--release|--locked] <key combo> <command> 236*bindsym* [--release|--locked] <key combo> <command>
226 Binds _key combo_ to execute the sway command _command_ when pressed. You 237 Binds _key combo_ to execute the sway command _command_ when pressed. You
227 may use XKB key names here (*xev*(1) is a good tool for discovering these). 238 may use XKB key names here (*xev*(1) is a good tool for discovering these).
diff --git a/sway/tree/arrange.c b/sway/tree/arrange.c
index 494a8461..a4b058f3 100644
--- a/sway/tree/arrange.c
+++ b/sway/tree/arrange.c
@@ -144,9 +144,9 @@ static void apply_tabbed_or_stacked_layout(struct sway_container *parent) {
144 144
145static void arrange_children_of(struct sway_container *parent); 145static void arrange_children_of(struct sway_container *parent);
146 146
147static void arrange_floating(struct sway_container *floating) { 147static void arrange_floating(list_t *floating) {
148 for (int i = 0; i < floating->children->length; ++i) { 148 for (int i = 0; i < floating->length; ++i) {
149 struct sway_container *floater = floating->children->items[i]; 149 struct sway_container *floater = floating->items[i];
150 if (floater->type == C_VIEW) { 150 if (floater->type == C_VIEW) {
151 view_autoconfigure(floater->sway_view); 151 view_autoconfigure(floater->sway_view);
152 } else { 152 } else {
@@ -154,7 +154,6 @@ static void arrange_floating(struct sway_container *floating) {
154 } 154 }
155 container_set_dirty(floater); 155 container_set_dirty(floater);
156 } 156 }
157 container_set_dirty(floating);
158} 157}
159 158
160static void arrange_children_of(struct sway_container *parent) { 159static void arrange_children_of(struct sway_container *parent) {
@@ -179,9 +178,6 @@ static void arrange_children_of(struct sway_container *parent) {
179 case L_NONE: 178 case L_NONE:
180 apply_horiz_layout(parent); 179 apply_horiz_layout(parent);
181 break; 180 break;
182 case L_FLOATING:
183 arrange_floating(parent);
184 break;
185 } 181 }
186 182
187 // Recurse into child containers 183 // Recurse into child containers
@@ -210,10 +206,30 @@ static void arrange_workspace(struct sway_container *workspace) {
210 wlr_log(WLR_DEBUG, "Usable area for ws: %dx%d@%d,%d", 206 wlr_log(WLR_DEBUG, "Usable area for ws: %dx%d@%d,%d",
211 area->width, area->height, area->x, area->y); 207 area->width, area->height, area->x, area->y);
212 remove_gaps(workspace); 208 remove_gaps(workspace);
209
210 double prev_x = workspace->x;
211 double prev_y = workspace->y;
213 workspace->width = area->width; 212 workspace->width = area->width;
214 workspace->height = area->height; 213 workspace->height = area->height;
215 workspace->x = output->x + area->x; 214 workspace->x = output->x + area->x;
216 workspace->y = output->y + area->y; 215 workspace->y = output->y + area->y;
216
217 // Adjust any floating containers
218 double diff_x = workspace->x - prev_x;
219 double diff_y = workspace->y - prev_y;
220 for (int i = 0; i < workspace->sway_workspace->floating->length; ++i) {
221 struct sway_container *floater =
222 workspace->sway_workspace->floating->items[i];
223 container_floating_translate(floater, diff_x, diff_y);
224 double center_x = floater->x + floater->width / 2;
225 double center_y = floater->y + floater->height / 2;
226 struct wlr_box workspace_box;
227 container_get_box(workspace, &workspace_box);
228 if (!wlr_box_contains_point(&workspace_box, center_x, center_y)) {
229 container_floating_move_to_center(floater);
230 }
231 }
232
217 add_gaps(workspace); 233 add_gaps(workspace);
218 container_set_dirty(workspace); 234 container_set_dirty(workspace);
219 wlr_log(WLR_DEBUG, "Arranging workspace '%s' at %f, %f", workspace->name, 235 wlr_log(WLR_DEBUG, "Arranging workspace '%s' at %f, %f", workspace->name,
diff --git a/sway/tree/container.c b/sway/tree/container.c
index eb06edc2..a8c6e667 100644
--- a/sway/tree/container.c
+++ b/sway/tree/container.c
@@ -67,32 +67,11 @@ void container_update_textures_recursive(struct sway_container *con) {
67 } 67 }
68 68
69 if (con->type == C_WORKSPACE) { 69 if (con->type == C_WORKSPACE) {
70 container_update_textures_recursive(con->sway_workspace->floating); 70 for (int i = 0; i < con->sway_workspace->floating->length; ++i) {
71 } 71 struct sway_container *floater =
72 } 72 con->sway_workspace->floating->items[i];
73} 73 container_update_textures_recursive(floater);
74 74 }
75static void handle_reparent(struct wl_listener *listener,
76 void *data) {
77 struct sway_container *container =
78 wl_container_of(listener, container, reparent);
79 struct sway_container *old_parent = data;
80
81 struct sway_container *old_output = old_parent;
82 if (old_output != NULL && old_output->type != C_OUTPUT) {
83 old_output = container_parent(old_output, C_OUTPUT);
84 }
85
86 struct sway_container *new_output = container->parent;
87 if (new_output != NULL && new_output->type != C_OUTPUT) {
88 new_output = container_parent(new_output, C_OUTPUT);
89 }
90
91 if (old_output && new_output) {
92 float old_scale = old_output->sway_output->wlr_output->scale;
93 float new_scale = new_output->sway_output->wlr_output->scale;
94 if (old_scale != new_scale) {
95 container_update_textures_recursive(container);
96 } 75 }
97 } 76 }
98} 77}
@@ -113,12 +92,9 @@ struct sway_container *container_create(enum sway_container_type type) {
113 c->children = create_list(); 92 c->children = create_list();
114 c->current.children = create_list(); 93 c->current.children = create_list();
115 } 94 }
95 c->outputs = create_list();
116 96
117 wl_signal_init(&c->events.destroy); 97 wl_signal_init(&c->events.destroy);
118 wl_signal_init(&c->events.reparent);
119
120 wl_signal_add(&c->events.reparent, &c->reparent);
121 c->reparent.notify = handle_reparent;
122 98
123 c->has_gaps = false; 99 c->has_gaps = false;
124 c->gaps_inner = 0; 100 c->gaps_inner = 0;
@@ -131,6 +107,7 @@ struct sway_container *container_create(enum sway_container_type type) {
131static void container_workspace_free(struct sway_workspace *ws) { 107static void container_workspace_free(struct sway_workspace *ws) {
132 list_foreach(ws->output_priority, free); 108 list_foreach(ws->output_priority, free);
133 list_free(ws->output_priority); 109 list_free(ws->output_priority);
110 list_free(ws->floating);
134 free(ws); 111 free(ws);
135} 112}
136 113
@@ -151,6 +128,7 @@ void container_free(struct sway_container *cont) {
151 wlr_texture_destroy(cont->title_urgent); 128 wlr_texture_destroy(cont->title_urgent);
152 list_free(cont->children); 129 list_free(cont->children);
153 list_free(cont->current.children); 130 list_free(cont->current.children);
131 list_free(cont->outputs);
154 132
155 switch (cont->type) { 133 switch (cont->type) {
156 case C_ROOT: 134 case C_ROOT:
@@ -222,18 +200,25 @@ static struct sway_container *container_workspace_destroy(
222 for (int i = 0; i < workspace->children->length; i++) { 200 for (int i = 0; i < workspace->children->length; i++) {
223 container_move_to(workspace->children->items[i], new_workspace); 201 container_move_to(workspace->children->items[i], new_workspace);
224 } 202 }
225 struct sway_container *floating = workspace->sway_workspace->floating; 203 list_t *floating = workspace->sway_workspace->floating;
226 for (int i = 0; i < floating->children->length; i++) { 204 for (int i = 0; i < floating->length; i++) {
227 container_move_to(floating->children->items[i], 205 struct sway_container *floater = floating->items[i];
228 new_workspace->sway_workspace->floating); 206 container_remove_child(floater);
207 workspace_add_floating(new_workspace, floater);
229 } 208 }
230 } 209 }
231 210
232 container_destroy_noreaping(workspace->sway_workspace->floating);
233
234 return output; 211 return output;
235} 212}
236 213
214static void untrack_output(struct sway_container *con, void *data) {
215 struct sway_output *output = data;
216 int index = list_find(con->outputs, output);
217 if (index != -1) {
218 list_del(con->outputs, index);
219 }
220}
221
237static struct sway_container *container_output_destroy( 222static struct sway_container *container_output_destroy(
238 struct sway_container *output) { 223 struct sway_container *output) {
239 if (!sway_assert(output, "cannot destroy null output")) { 224 if (!sway_assert(output, "cannot destroy null output")) {
@@ -270,11 +255,13 @@ static struct sway_container *container_output_destroy(
270 container_destroy(workspace); 255 container_destroy(workspace);
271 } 256 }
272 257
273 container_sort_workspaces(new_output); 258 output_sort_workspaces(new_output);
274 } 259 }
275 } 260 }
276 } 261 }
277 262
263 root_for_each_container(untrack_output, output->sway_output);
264
278 wl_list_remove(&output->sway_output->mode.link); 265 wl_list_remove(&output->sway_output->mode.link);
279 wl_list_remove(&output->sway_output->transform.link); 266 wl_list_remove(&output->sway_output->transform.link);
280 wl_list_remove(&output->sway_output->scale.link); 267 wl_list_remove(&output->sway_output->scale.link);
@@ -339,10 +326,6 @@ static struct sway_container *container_destroy_noreaping(
339} 326}
340 327
341bool container_reap_empty(struct sway_container *con) { 328bool container_reap_empty(struct sway_container *con) {
342 if (con->layout == L_FLOATING) {
343 // Don't reap the magical floating container that each workspace has
344 return false;
345 }
346 switch (con->type) { 329 switch (con->type) {
347 case C_ROOT: 330 case C_ROOT:
348 case C_OUTPUT: 331 case C_OUTPUT:
@@ -432,8 +415,10 @@ struct sway_container *container_close(struct sway_container *con) {
432 415
433 if (con->type == C_VIEW) { 416 if (con->type == C_VIEW) {
434 view_close(con->sway_view); 417 view_close(con->sway_view);
435 } else { 418 } else if (con->type == C_CONTAINER) {
436 container_for_each_descendant(con, container_close_func, NULL); 419 container_for_each_child(con, container_close_func, NULL);
420 } else if (con->type == C_WORKSPACE) {
421 workspace_for_each_container(con, container_close_func, NULL);
437 } 422 }
438 423
439 return parent; 424 return parent;
@@ -465,23 +450,12 @@ struct sway_container *container_view_create(struct sway_container *sibling,
465 return swayc; 450 return swayc;
466} 451}
467 452
468void container_descendants(struct sway_container *root, 453struct sway_container *container_find_child(struct sway_container *container,
469 enum sway_container_type type,
470 void (*func)(struct sway_container *item, void *data), void *data) {
471 if (!root->children || !root->children->length) {
472 return;
473 }
474 for (int i = 0; i < root->children->length; ++i) {
475 struct sway_container *item = root->children->items[i];
476 if (item->type == type) {
477 func(item, data);
478 }
479 container_descendants(item, type, func, data);
480 }
481}
482
483struct sway_container *container_find(struct sway_container *container,
484 bool (*test)(struct sway_container *view, void *data), void *data) { 454 bool (*test)(struct sway_container *view, void *data), void *data) {
455 if (!sway_assert(container->type == C_CONTAINER ||
456 container->type == C_VIEW, "Expected a container or view")) {
457 return NULL;
458 }
485 if (!container->children) { 459 if (!container->children) {
486 return NULL; 460 return NULL;
487 } 461 }
@@ -489,15 +463,11 @@ struct sway_container *container_find(struct sway_container *container,
489 struct sway_container *child = container->children->items[i]; 463 struct sway_container *child = container->children->items[i];
490 if (test(child, data)) { 464 if (test(child, data)) {
491 return child; 465 return child;
492 } else {
493 struct sway_container *res = container_find(child, test, data);
494 if (res) {
495 return res;
496 }
497 } 466 }
498 } 467 struct sway_container *res = container_find_child(child, test, data);
499 if (container->type == C_WORKSPACE) { 468 if (res) {
500 return container_find(container->sway_workspace->floating, test, data); 469 return res;
470 }
501 } 471 }
502 return NULL; 472 return NULL;
503} 473}
@@ -522,8 +492,8 @@ static void surface_at_view(struct sway_container *swayc, double lx, double ly,
522 return; 492 return;
523 } 493 }
524 struct sway_view *sview = swayc->sway_view; 494 struct sway_view *sview = swayc->sway_view;
525 double view_sx = lx - sview->x; 495 double view_sx = lx - sview->x + sview->geometry.x;
526 double view_sy = ly - sview->y; 496 double view_sy = ly - sview->y + sview->geometry.y;
527 497
528 double _sx, _sy; 498 double _sx, _sy;
529 struct wlr_surface *_surface = NULL; 499 struct wlr_surface *_surface = NULL;
@@ -639,9 +609,8 @@ static struct sway_container *floating_container_at(double lx, double ly,
639 } 609 }
640 // Items at the end of the list are on top, so iterate the list in 610 // Items at the end of the list are on top, so iterate the list in
641 // reverse. 611 // reverse.
642 for (int k = ws->floating->children->length - 1; k >= 0; --k) { 612 for (int k = ws->floating->length - 1; k >= 0; --k) {
643 struct sway_container *floater = 613 struct sway_container *floater = ws->floating->items[k];
644 ws->floating->children->items[k];
645 struct wlr_box box = { 614 struct wlr_box box = {
646 .x = floater->x, 615 .x = floater->x,
647 .y = floater->y, 616 .y = floater->y,
@@ -677,9 +646,6 @@ struct sway_container *tiling_container_at(
677 return container_at_tabbed(con, lx, ly, surface, sx, sy); 646 return container_at_tabbed(con, lx, ly, surface, sx, sy);
678 case L_STACKED: 647 case L_STACKED:
679 return container_at_stacked(con, lx, ly, surface, sx, sy); 648 return container_at_stacked(con, lx, ly, surface, sx, sy);
680 case L_FLOATING:
681 sway_assert(false, "Didn't expect to see floating here");
682 return NULL;
683 case L_NONE: 649 case L_NONE:
684 return NULL; 650 return NULL;
685 } 651 }
@@ -743,26 +709,20 @@ struct sway_container *container_at(struct sway_container *workspace,
743 return NULL; 709 return NULL;
744} 710}
745 711
746void container_for_each_descendant(struct sway_container *container, 712void container_for_each_child(struct sway_container *container,
747 void (*f)(struct sway_container *container, void *data), 713 void (*f)(struct sway_container *container, void *data),
748 void *data) { 714 void *data) {
749 if (!container) { 715 if (!sway_assert(container->type == C_CONTAINER ||
716 container->type == C_VIEW, "Expected a container or view")) {
750 return; 717 return;
751 } 718 }
752 if (container->children) { 719 if (container->children) {
753 for (int i = 0; i < container->children->length; ++i) { 720 for (int i = 0; i < container->children->length; ++i) {
754 struct sway_container *child = container->children->items[i]; 721 struct sway_container *child = container->children->items[i];
755 container_for_each_descendant(child, f, data); 722 f(child, data);
756 } 723 container_for_each_child(child, f, data);
757 }
758 if (container->type == C_WORKSPACE) {
759 struct sway_container *floating = container->sway_workspace->floating;
760 for (int i = 0; i < floating->children->length; ++i) {
761 struct sway_container *child = floating->children->items[i];
762 container_for_each_descendant(child, f, data);
763 } 724 }
764 } 725 }
765 f(container, data);
766} 726}
767 727
768bool container_has_ancestor(struct sway_container *descendant, 728bool container_has_ancestor(struct sway_container *descendant,
@@ -800,13 +760,24 @@ void container_damage_whole(struct sway_container *container) {
800 } 760 }
801} 761}
802 762
763/**
764 * Return the output which will be used for scale purposes.
765 * This is the most recently entered output.
766 */
767struct sway_output *container_get_effective_output(struct sway_container *con) {
768 if (con->outputs->length == 0) {
769 return NULL;
770 }
771 return con->outputs->items[con->outputs->length - 1];
772}
773
803static void update_title_texture(struct sway_container *con, 774static void update_title_texture(struct sway_container *con,
804 struct wlr_texture **texture, struct border_colors *class) { 775 struct wlr_texture **texture, struct border_colors *class) {
805 if (!sway_assert(con->type == C_CONTAINER || con->type == C_VIEW, 776 if (!sway_assert(con->type == C_CONTAINER || con->type == C_VIEW,
806 "Unexpected type %s", container_type_to_str(con->type))) { 777 "Unexpected type %s", container_type_to_str(con->type))) {
807 return; 778 return;
808 } 779 }
809 struct sway_container *output = container_parent(con, C_OUTPUT); 780 struct sway_output *output = container_get_effective_output(con);
810 if (!output) { 781 if (!output) {
811 return; 782 return;
812 } 783 }
@@ -818,7 +789,7 @@ static void update_title_texture(struct sway_container *con,
818 return; 789 return;
819 } 790 }
820 791
821 double scale = output->sway_output->wlr_output->scale; 792 double scale = output->wlr_output->scale;
822 int width = 0; 793 int width = 0;
823 int height = con->title_height * scale; 794 int height = con->title_height * scale;
824 795
@@ -846,7 +817,7 @@ static void update_title_texture(struct sway_container *con,
846 unsigned char *data = cairo_image_surface_get_data(surface); 817 unsigned char *data = cairo_image_surface_get_data(surface);
847 int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width); 818 int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width);
848 struct wlr_renderer *renderer = wlr_backend_get_renderer( 819 struct wlr_renderer *renderer = wlr_backend_get_renderer(
849 output->sway_output->wlr_output->backend); 820 output->wlr_output->backend);
850 *texture = wlr_texture_from_pixels( 821 *texture = wlr_texture_from_pixels(
851 renderer, WL_SHM_FORMAT_ARGB8888, stride, width, height, data); 822 renderer, WL_SHM_FORMAT_ARGB8888, stride, width, height, data);
852 cairo_surface_destroy(surface); 823 cairo_surface_destroy(surface);
@@ -899,9 +870,6 @@ static size_t get_tree_representation(struct sway_container *parent, char *buffe
899 case L_STACKED: 870 case L_STACKED:
900 lenient_strcat(buffer, "S["); 871 lenient_strcat(buffer, "S[");
901 break; 872 break;
902 case L_FLOATING:
903 lenient_strcat(buffer, "F[");
904 break;
905 case L_NONE: 873 case L_NONE:
906 lenient_strcat(buffer, "D["); 874 lenient_strcat(buffer, "D[");
907 break; 875 break;
@@ -1030,12 +998,13 @@ void container_set_floating(struct sway_container *container, bool enable) {
1030 struct sway_container *workspace = container_parent(container, C_WORKSPACE); 998 struct sway_container *workspace = container_parent(container, C_WORKSPACE);
1031 999
1032 if (enable) { 1000 if (enable) {
1033 container_remove_child(container); 1001 struct sway_container *old_parent = container_remove_child(container);
1034 container_add_child(workspace->sway_workspace->floating, container); 1002 workspace_add_floating(workspace, container);
1035 container_init_floating(container); 1003 container_init_floating(container);
1036 if (container->type == C_VIEW) { 1004 if (container->type == C_VIEW) {
1037 view_set_tiled(container->sway_view, false); 1005 view_set_tiled(container->sway_view, false);
1038 } 1006 }
1007 container_reap_empty(old_parent);
1039 } else { 1008 } else {
1040 // Returning to tiled 1009 // Returning to tiled
1041 if (container->scratchpad) { 1010 if (container->scratchpad) {
@@ -1083,14 +1052,12 @@ void container_set_geometry_from_floating_view(struct sway_container *con) {
1083 con->y = view->y - top; 1052 con->y = view->y - top;
1084 con->width = view->width + border_width * 2; 1053 con->width = view->width + border_width * 2;
1085 con->height = top + view->height + border_width; 1054 con->height = top + view->height + border_width;
1055 container_set_dirty(con);
1086} 1056}
1087 1057
1088bool container_is_floating(struct sway_container *container) { 1058bool container_is_floating(struct sway_container *container) {
1089 struct sway_container *workspace = container_parent(container, C_WORKSPACE); 1059 return container->parent && container->parent->type == C_WORKSPACE &&
1090 if (!workspace) { 1060 list_find(container->parent->sway_workspace->floating, container) != -1;
1091 return false;
1092 }
1093 return container->parent == workspace->sway_workspace->floating;
1094} 1061}
1095 1062
1096void container_get_box(struct sway_container *container, struct wlr_box *box) { 1063void container_get_box(struct sway_container *container, struct wlr_box *box) {
@@ -1170,7 +1137,7 @@ void container_floating_move_to(struct sway_container *con,
1170 output_get_active_workspace(new_output->sway_output); 1137 output_get_active_workspace(new_output->sway_output);
1171 if (old_workspace != new_workspace) { 1138 if (old_workspace != new_workspace) {
1172 container_remove_child(con); 1139 container_remove_child(con);
1173 container_add_child(new_workspace->sway_workspace->floating, con); 1140 workspace_add_floating(new_workspace, con);
1174 arrange_windows(old_workspace); 1141 arrange_windows(old_workspace);
1175 arrange_windows(new_workspace); 1142 arrange_windows(new_workspace);
1176 workspace_detect_urgent(old_workspace); 1143 workspace_detect_urgent(old_workspace);
@@ -1197,13 +1164,12 @@ void container_set_dirty(struct sway_container *container) {
1197 list_add(server.dirty_containers, container); 1164 list_add(server.dirty_containers, container);
1198} 1165}
1199 1166
1200static bool find_urgent_iterator(struct sway_container *con, 1167static bool find_urgent_iterator(struct sway_container *con, void *data) {
1201 void *data) {
1202 return con->type == C_VIEW && view_is_urgent(con->sway_view); 1168 return con->type == C_VIEW && view_is_urgent(con->sway_view);
1203} 1169}
1204 1170
1205bool container_has_urgent_child(struct sway_container *container) { 1171bool container_has_urgent_child(struct sway_container *container) {
1206 return container_find(container, find_urgent_iterator, NULL); 1172 return container_find_child(container, find_urgent_iterator, NULL);
1207} 1173}
1208 1174
1209void container_end_mouse_operation(struct sway_container *container) { 1175void container_end_mouse_operation(struct sway_container *container) {
@@ -1235,7 +1201,8 @@ void container_set_fullscreen(struct sway_container *container, bool enable) {
1235 container_set_fullscreen(workspace->sway_workspace->fullscreen, false); 1201 container_set_fullscreen(workspace->sway_workspace->fullscreen, false);
1236 } 1202 }
1237 1203
1238 container_for_each_descendant(container, set_fullscreen_iterator, &enable); 1204 set_fullscreen_iterator(container, &enable);
1205 container_for_each_child(container, set_fullscreen_iterator, &enable);
1239 1206
1240 container->is_fullscreen = enable; 1207 container->is_fullscreen = enable;
1241 1208
@@ -1284,14 +1251,10 @@ void container_set_fullscreen(struct sway_container *container, bool enable) {
1284} 1251}
1285 1252
1286bool container_is_floating_or_child(struct sway_container *container) { 1253bool container_is_floating_or_child(struct sway_container *container) {
1287 do { 1254 while (container->parent && container->parent->type != C_WORKSPACE) {
1288 if (container->parent && container->parent->layout == L_FLOATING) {
1289 return true;
1290 }
1291 container = container->parent; 1255 container = container->parent;
1292 } while (container && container->type != C_WORKSPACE); 1256 }
1293 1257 return container_is_floating(container);
1294 return false;
1295} 1258}
1296 1259
1297bool container_is_fullscreen_or_child(struct sway_container *container) { 1260bool container_is_fullscreen_or_child(struct sway_container *container) {
@@ -1305,14 +1268,66 @@ bool container_is_fullscreen_or_child(struct sway_container *container) {
1305 return false; 1268 return false;
1306} 1269}
1307 1270
1308struct sway_container *container_wrap_children(struct sway_container *parent) { 1271static void surface_send_enter_iterator(struct wlr_surface *surface,
1309 struct sway_container *middle = container_create(C_CONTAINER); 1272 int x, int y, void *data) {
1310 middle->layout = parent->layout; 1273 struct wlr_output *wlr_output = data;
1311 while (parent->children->length) { 1274 wlr_surface_send_enter(surface, wlr_output);
1312 struct sway_container *child = parent->children->items[0]; 1275}
1313 container_remove_child(child); 1276
1314 container_add_child(middle, child); 1277static void surface_send_leave_iterator(struct wlr_surface *surface,
1278 int x, int y, void *data) {
1279 struct wlr_output *wlr_output = data;
1280 wlr_surface_send_leave(surface, wlr_output);
1281}
1282
1283void container_discover_outputs(struct sway_container *con) {
1284 if (!sway_assert(con->type == C_CONTAINER || con->type == C_VIEW,
1285 "Expected a container or view")) {
1286 return;
1287 }
1288 struct wlr_box con_box = {
1289 .x = con->current.swayc_x,
1290 .y = con->current.swayc_y,
1291 .width = con->current.swayc_width,
1292 .height = con->current.swayc_height,
1293 };
1294 struct sway_output *old_output = container_get_effective_output(con);
1295
1296 for (int i = 0; i < root_container.children->length; ++i) {
1297 struct sway_container *output = root_container.children->items[i];
1298 struct sway_output *sway_output = output->sway_output;
1299 struct wlr_box output_box;
1300 container_get_box(output, &output_box);
1301 struct wlr_box intersection;
1302 bool intersects =
1303 wlr_box_intersection(&con_box, &output_box, &intersection);
1304 int index = list_find(con->outputs, sway_output);
1305
1306 if (intersects && index == -1) {
1307 // Send enter
1308 wlr_log(WLR_DEBUG, "Con %p entered output %p", con, sway_output);
1309 if (con->type == C_VIEW) {
1310 view_for_each_surface(con->sway_view,
1311 surface_send_enter_iterator, sway_output->wlr_output);
1312 }
1313 list_add(con->outputs, sway_output);
1314 } else if (!intersects && index != -1) {
1315 // Send leave
1316 wlr_log(WLR_DEBUG, "Con %p left output %p", con, sway_output);
1317 if (con->type == C_VIEW) {
1318 view_for_each_surface(con->sway_view,
1319 surface_send_leave_iterator, sway_output->wlr_output);
1320 }
1321 list_del(con->outputs, index);
1322 }
1323 }
1324 struct sway_output *new_output = container_get_effective_output(con);
1325 double old_scale = old_output ? old_output->wlr_output->scale : -1;
1326 double new_scale = new_output ? new_output->wlr_output->scale : -1;
1327 if (old_scale != new_scale) {
1328 container_update_title_textures(con);
1329 if (con->type == C_VIEW) {
1330 view_update_marks_textures(con->sway_view);
1331 }
1315 } 1332 }
1316 container_add_child(parent, middle);
1317 return middle;
1318} 1333}
diff --git a/sway/tree/layout.c b/sway/tree/layout.c
index 2b710403..a3de44ce 100644
--- a/sway/tree/layout.c
+++ b/sway/tree/layout.c
@@ -1,5 +1,4 @@
1#define _POSIX_C_SOURCE 200809L 1#define _POSIX_C_SOURCE 200809L
2#include <ctype.h>
3#include <math.h> 2#include <math.h>
4#include <stdbool.h> 3#include <stdbool.h>
5#include <stdlib.h> 4#include <stdlib.h>
@@ -76,7 +75,6 @@ void container_insert_child(struct sway_container *parent,
76 list_insert(parent->children, i, child); 75 list_insert(parent->children, i, child);
77 child->parent = parent; 76 child->parent = parent;
78 container_handle_fullscreen_reparent(child, old_parent); 77 container_handle_fullscreen_reparent(child, old_parent);
79 wl_signal_emit(&child->events.reparent, old_parent);
80} 78}
81 79
82struct sway_container *container_add_sibling(struct sway_container *fixed, 80struct sway_container *container_add_sibling(struct sway_container *fixed,
@@ -92,7 +90,6 @@ struct sway_container *container_add_sibling(struct sway_container *fixed,
92 list_insert(parent->children, i + 1, active); 90 list_insert(parent->children, i + 1, active);
93 active->parent = parent; 91 active->parent = parent;
94 container_handle_fullscreen_reparent(active, old_parent); 92 container_handle_fullscreen_reparent(active, old_parent);
95 wl_signal_emit(&active->events.reparent, old_parent);
96 return active->parent; 93 return active->parent;
97} 94}
98 95
@@ -118,9 +115,11 @@ struct sway_container *container_remove_child(struct sway_container *child) {
118 } 115 }
119 116
120 struct sway_container *parent = child->parent; 117 struct sway_container *parent = child->parent;
121 int index = index_child(child); 118 list_t *list = container_is_floating(child) ?
119 parent->sway_workspace->floating : parent->children;
120 int index = list_find(list, child);
122 if (index != -1) { 121 if (index != -1) {
123 list_del(parent->children, index); 122 list_del(list, index);
124 } 123 }
125 child->parent = NULL; 124 child->parent = NULL;
126 container_notify_subtree_changed(parent); 125 container_notify_subtree_changed(parent);
@@ -161,7 +160,8 @@ void container_move_to(struct sway_container *container,
161 struct sway_container *old_output = 160 struct sway_container *old_output =
162 container_parent(container, C_OUTPUT); 161 container_parent(container, C_OUTPUT);
163 old_parent = container_remove_child(container); 162 old_parent = container_remove_child(container);
164 container_add_child(new_ws->sway_workspace->floating, container); 163 workspace_add_floating(new_ws, container);
164 container_handle_fullscreen_reparent(container, old_parent);
165 // If changing output, center it within the workspace 165 // If changing output, center it within the workspace
166 if (old_output != new_ws->parent && !container->is_fullscreen) { 166 if (old_output != new_ws->parent && !container->is_fullscreen) {
167 container_floating_move_to_center(container); 167 container_floating_move_to_center(container);
@@ -179,8 +179,6 @@ void container_move_to(struct sway_container *container,
179 } 179 }
180 } 180 }
181 181
182 wl_signal_emit(&container->events.reparent, old_parent);
183
184 if (container->type == C_VIEW) { 182 if (container->type == C_VIEW) {
185 ipc_event_window(container, "move"); 183 ipc_event_window(container, "move");
186 } 184 }
@@ -305,7 +303,6 @@ static void workspace_rejigger(struct sway_container *ws,
305 303
306 container_flatten(ws); 304 container_flatten(ws);
307 container_reap_empty_recursive(original_parent); 305 container_reap_empty_recursive(original_parent);
308 wl_signal_emit(&child->events.reparent, original_parent);
309 container_create_notify(new_parent); 306 container_create_notify(new_parent);
310} 307}
311 308
@@ -432,9 +429,6 @@ void container_move(struct sway_container *container,
432 if ((index == parent->children->length - 1 && offs > 0) 429 if ((index == parent->children->length - 1 && offs > 0)
433 || (index == 0 && offs < 0)) { 430 || (index == 0 && offs < 0)) {
434 if (current->parent == container->parent) { 431 if (current->parent == container->parent) {
435 if (parent->parent->layout == L_FLOATING) {
436 return;
437 }
438 if (!parent->is_fullscreen && 432 if (!parent->is_fullscreen &&
439 (parent->layout == L_TABBED || 433 (parent->layout == L_TABBED ||
440 parent->layout == L_STACKED)) { 434 parent->layout == L_STACKED)) {
@@ -458,14 +452,10 @@ void container_move(struct sway_container *container,
458 sibling = parent->children->items[index + offs]; 452 sibling = parent->children->items[index + offs];
459 wlr_log(WLR_DEBUG, "Selecting sibling id:%zd", sibling->id); 453 wlr_log(WLR_DEBUG, "Selecting sibling id:%zd", sibling->id);
460 } 454 }
461 } else if (!parent->is_fullscreen && 455 } else if (!parent->is_fullscreen && (parent->layout == L_TABBED ||
462 parent->parent->layout != L_FLOATING &&
463 (parent->layout == L_TABBED ||
464 parent->layout == L_STACKED)) { 456 parent->layout == L_STACKED)) {
465 move_out_of_tabs_stacks(container, current, move_dir, offs); 457 move_out_of_tabs_stacks(container, current, move_dir, offs);
466 return; 458 return;
467 } else if (parent->parent->layout == L_FLOATING) {
468 return;
469 } else { 459 } else {
470 wlr_log(WLR_DEBUG, "Moving up to find a parallel container"); 460 wlr_log(WLR_DEBUG, "Moving up to find a parallel container");
471 current = current->parent; 461 current = current->parent;
@@ -591,28 +581,6 @@ enum sway_container_layout container_get_default_layout(
591 } 581 }
592} 582}
593 583
594static int sort_workspace_cmp_qsort(const void *_a, const void *_b) {
595 struct sway_container *a = *(void **)_a;
596 struct sway_container *b = *(void **)_b;
597 int retval = 0;
598
599 if (isdigit(a->name[0]) && isdigit(b->name[0])) {
600 int a_num = strtol(a->name, NULL, 10);
601 int b_num = strtol(b->name, NULL, 10);
602 retval = (a_num < b_num) ? -1 : (a_num > b_num);
603 } else if (isdigit(a->name[0])) {
604 retval = -1;
605 } else if (isdigit(b->name[0])) {
606 retval = 1;
607 }
608
609 return retval;
610}
611
612void container_sort_workspaces(struct sway_container *output) {
613 list_stable_sort(output->children, sort_workspace_cmp_qsort);
614}
615
616/** 584/**
617 * Get swayc in the direction of newly entered output. 585 * Get swayc in the direction of newly entered output.
618 */ 586 */
@@ -825,13 +793,15 @@ struct sway_container *container_replace_child(struct sway_container *child,
825 if (parent == NULL) { 793 if (parent == NULL) {
826 return NULL; 794 return NULL;
827 } 795 }
828 int i = index_child(child);
829 796
830 // TODO floating 797 list_t *list = container_is_floating(child) ?
798 parent->sway_workspace->floating : parent->children;
799 int i = list_find(list, child);
800
831 if (new_child->parent) { 801 if (new_child->parent) {
832 container_remove_child(new_child); 802 container_remove_child(new_child);
833 } 803 }
834 parent->children->items[i] = new_child; 804 list->items[i] = new_child;
835 new_child->parent = parent; 805 new_child->parent = parent;
836 child->parent = NULL; 806 child->parent = NULL;
837 807
@@ -884,7 +854,6 @@ struct sway_container *container_split(struct sway_container *child,
884 struct sway_container *ws_child = workspace->children->items[0]; 854 struct sway_container *ws_child = workspace->children->items[0];
885 container_remove_child(ws_child); 855 container_remove_child(ws_child);
886 container_add_child(cont, ws_child); 856 container_add_child(cont, ws_child);
887 wl_signal_emit(&ws_child->events.reparent, workspace);
888 } 857 }
889 858
890 container_add_child(workspace, cont); 859 container_add_child(workspace, cont);
@@ -892,11 +861,9 @@ struct sway_container *container_split(struct sway_container *child,
892 workspace->layout = layout; 861 workspace->layout = layout;
893 cont->layout = old_layout; 862 cont->layout = old_layout;
894 } else { 863 } else {
895 struct sway_container *old_parent = child->parent;
896 cont->layout = layout; 864 cont->layout = layout;
897 container_replace_child(child, cont); 865 container_replace_child(child, cont);
898 container_add_child(cont, child); 866 container_add_child(cont, child);
899 wl_signal_emit(&child->events.reparent, old_parent);
900 } 867 }
901 868
902 if (set_focus) { 869 if (set_focus) {
@@ -909,13 +876,13 @@ struct sway_container *container_split(struct sway_container *child,
909} 876}
910 877
911void container_recursive_resize(struct sway_container *container, 878void container_recursive_resize(struct sway_container *container,
912 double amount, enum resize_edge edge) { 879 double amount, enum wlr_edges edge) {
913 bool layout_match = true; 880 bool layout_match = true;
914 wlr_log(WLR_DEBUG, "Resizing %p with amount: %f", container, amount); 881 wlr_log(WLR_DEBUG, "Resizing %p with amount: %f", container, amount);
915 if (edge == RESIZE_EDGE_LEFT || edge == RESIZE_EDGE_RIGHT) { 882 if (edge == WLR_EDGE_LEFT || edge == WLR_EDGE_RIGHT) {
916 container->width += amount; 883 container->width += amount;
917 layout_match = container->layout == L_HORIZ; 884 layout_match = container->layout == L_HORIZ;
918 } else if (edge == RESIZE_EDGE_TOP || edge == RESIZE_EDGE_BOTTOM) { 885 } else if (edge == WLR_EDGE_TOP || edge == WLR_EDGE_BOTTOM) {
919 container->height += amount; 886 container->height += amount;
920 layout_match = container->layout == L_VERT; 887 layout_match = container->layout == L_VERT;
921 } 888 }
@@ -996,7 +963,8 @@ void container_swap(struct sway_container *con1, struct sway_container *con2) {
996 "Cannot swap ancestor and descendant")) { 963 "Cannot swap ancestor and descendant")) {
997 return; 964 return;
998 } 965 }
999 if (!sway_assert(con1->layout != L_FLOATING && con2->layout != L_FLOATING, 966 if (!sway_assert(!container_is_floating(con1)
967 && !container_is_floating(con2),
1000 "Swapping with floating containers is not supported")) { 968 "Swapping with floating containers is not supported")) {
1001 return; 969 return;
1002 } 970 }
diff --git a/sway/tree/output.c b/sway/tree/output.c
index 31e3bf9b..6da63064 100644
--- a/sway/tree/output.c
+++ b/sway/tree/output.c
@@ -1,4 +1,5 @@
1#define _POSIX_C_SOURCE 200809L 1#define _POSIX_C_SOURCE 200809L
2#include <ctype.h>
2#include <string.h> 3#include <string.h>
3#include <strings.h> 4#include <strings.h>
4#include "sway/ipc-server.h" 5#include "sway/ipc-server.h"
@@ -28,7 +29,7 @@ static void restore_workspaces(struct sway_container *output) {
28 } 29 }
29 } 30 }
30 31
31 container_sort_workspaces(output); 32 output_sort_workspaces(output);
32} 33}
33 34
34struct sway_container *output_create( 35struct sway_container *output_create(
@@ -102,3 +103,73 @@ struct sway_container *output_create(
102 return output; 103 return output;
103} 104}
104 105
106void output_for_each_workspace(struct sway_container *output,
107 void (*f)(struct sway_container *con, void *data), void *data) {
108 if (!sway_assert(output->type == C_OUTPUT, "Expected an output")) {
109 return;
110 }
111 for (int i = 0; i < output->children->length; ++i) {
112 struct sway_container *workspace = output->children->items[i];
113 f(workspace, data);
114 }
115}
116
117void output_for_each_container(struct sway_container *output,
118 void (*f)(struct sway_container *con, void *data), void *data) {
119 if (!sway_assert(output->type == C_OUTPUT, "Expected an output")) {
120 return;
121 }
122 for (int i = 0; i < output->children->length; ++i) {
123 struct sway_container *workspace = output->children->items[i];
124 workspace_for_each_container(workspace, f, data);
125 }
126}
127
128struct sway_container *output_find_workspace(struct sway_container *output,
129 bool (*test)(struct sway_container *con, void *data), void *data) {
130 if (!sway_assert(output->type == C_OUTPUT, "Expected an output")) {
131 return NULL;
132 }
133 for (int i = 0; i < output->children->length; ++i) {
134 struct sway_container *workspace = output->children->items[i];
135 if (test(workspace, data)) {
136 return workspace;
137 }
138 }
139 return NULL;
140}
141
142struct sway_container *output_find_container(struct sway_container *output,
143 bool (*test)(struct sway_container *con, void *data), void *data) {
144 if (!sway_assert(output->type == C_OUTPUT, "Expected an output")) {
145 return NULL;
146 }
147 struct sway_container *result = NULL;
148 for (int i = 0; i < output->children->length; ++i) {
149 struct sway_container *workspace = output->children->items[i];
150 if ((result = workspace_find_container(workspace, test, data))) {
151 return result;
152 }
153 }
154 return NULL;
155}
156
157static int sort_workspace_cmp_qsort(const void *_a, const void *_b) {
158 struct sway_container *a = *(void **)_a;
159 struct sway_container *b = *(void **)_b;
160
161 if (isdigit(a->name[0]) && isdigit(b->name[0])) {
162 int a_num = strtol(a->name, NULL, 10);
163 int b_num = strtol(b->name, NULL, 10);
164 return (a_num < b_num) ? -1 : (a_num > b_num);
165 } else if (isdigit(a->name[0])) {
166 return -1;
167 } else if (isdigit(b->name[0])) {
168 return 1;
169 }
170 return 0;
171}
172
173void output_sort_workspaces(struct sway_container *output) {
174 list_stable_sort(output->children, sort_workspace_cmp_qsort);
175}
diff --git a/sway/tree/root.c b/sway/tree/root.c
index fc908cc1..c27ff2c3 100644
--- a/sway/tree/root.c
+++ b/sway/tree/root.c
@@ -105,7 +105,7 @@ void root_scratchpad_show(struct sway_container *con) {
105 if (con->parent) { 105 if (con->parent) {
106 container_remove_child(con); 106 container_remove_child(con);
107 } 107 }
108 container_add_child(ws->sway_workspace->floating, con); 108 workspace_add_floating(ws, con);
109 109
110 // Make sure the container's center point overlaps this workspace 110 // Make sure the container's center point overlaps this workspace
111 double center_lx = con->x + con->width / 2; 111 double center_lx = con->x + con->width / 2;
@@ -256,3 +256,81 @@ void root_record_workspace_pid(pid_t pid) {
256 &pw->output_destroy); 256 &pw->output_destroy);
257 wl_list_insert(&pid_workspaces, &pw->link); 257 wl_list_insert(&pid_workspaces, &pw->link);
258} 258}
259
260void root_for_each_workspace(void (*f)(struct sway_container *con, void *data),
261 void *data) {
262 for (int i = 0; i < root_container.children->length; ++i) {
263 struct sway_container *output = root_container.children->items[i];
264 output_for_each_workspace(output, f, data);
265 }
266}
267
268void root_for_each_container(void (*f)(struct sway_container *con, void *data),
269 void *data) {
270 for (int i = 0; i < root_container.children->length; ++i) {
271 struct sway_container *output = root_container.children->items[i];
272 output_for_each_container(output, f, data);
273 }
274
275 // Scratchpad
276 for (int i = 0; i < root_container.sway_root->scratchpad->length; ++i) {
277 struct sway_container *container =
278 root_container.sway_root->scratchpad->items[i];
279 // If the container has a parent then it's visible on a workspace
280 // and will have been iterated in the previous for loop. So we only
281 // iterate the hidden scratchpad containers here.
282 if (!container->parent) {
283 f(container, data);
284 container_for_each_child(container, f, data);
285 }
286 }
287}
288
289struct sway_container *root_find_output(
290 bool (*test)(struct sway_container *con, void *data), void *data) {
291 for (int i = 0; i < root_container.children->length; ++i) {
292 struct sway_container *output = root_container.children->items[i];
293 if (test(output, data)) {
294 return output;
295 }
296 }
297 return NULL;
298}
299
300struct sway_container *root_find_workspace(
301 bool (*test)(struct sway_container *con, void *data), void *data) {
302 struct sway_container *result = NULL;
303 for (int i = 0; i < root_container.children->length; ++i) {
304 struct sway_container *output = root_container.children->items[i];
305 if ((result = output_find_workspace(output, test, data))) {
306 return result;
307 }
308 }
309 return NULL;
310}
311
312struct sway_container *root_find_container(
313 bool (*test)(struct sway_container *con, void *data), void *data) {
314 struct sway_container *result = NULL;
315 for (int i = 0; i < root_container.children->length; ++i) {
316 struct sway_container *output = root_container.children->items[i];
317 if ((result = output_find_container(output, test, data))) {
318 return result;
319 }
320 }
321
322 // Scratchpad
323 for (int i = 0; i < root_container.sway_root->scratchpad->length; ++i) {
324 struct sway_container *container =
325 root_container.sway_root->scratchpad->items[i];
326 if (!container->parent) {
327 if (test(container, data)) {
328 return container;
329 }
330 if ((result = container_find_child(container, test, data))) {
331 return result;
332 }
333 }
334 }
335 return NULL;
336}
diff --git a/sway/tree/view.c b/sway/tree/view.c
index 950494d8..7bf7325a 100644
--- a/sway/tree/view.c
+++ b/sway/tree/view.c
@@ -236,7 +236,12 @@ void view_autoconfigure(struct sway_view *view) {
236 view->border_top = false; 236 view->border_top = false;
237 } 237 }
238 238
239 switch (view->border) { 239 enum sway_container_border border = view->border;
240 if (view->using_csd) {
241 border = B_NONE;
242 }
243
244 switch (border) {
240 case B_NONE: 245 case B_NONE:
241 x = con->x; 246 x = con->x;
242 y = con->y + y_offset; 247 y = con->y + y_offset;
@@ -364,48 +369,6 @@ static void view_handle_surface_new_subsurface(struct wl_listener *listener,
364 view_subsurface_create(view, subsurface); 369 view_subsurface_create(view, subsurface);
365} 370}
366 371
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, 372static bool view_has_executed_criteria(struct sway_view *view,
410 struct criteria *criteria) { 373 struct criteria *criteria) {
411 for (int i = 0; i < view->executed_criteria->length; ++i) { 374 for (int i = 0; i < view->executed_criteria->length; ++i) {
@@ -450,12 +413,22 @@ static struct sway_container *select_workspace(struct sway_view *view) {
450 413
451 // Check if there's any `assign` criteria for the view 414 // Check if there's any `assign` criteria for the view
452 list_t *criterias = criteria_for_view(view, 415 list_t *criterias = criteria_for_view(view,
453 CT_ASSIGN_WORKSPACE | CT_ASSIGN_OUTPUT); 416 CT_ASSIGN_WORKSPACE | CT_ASSIGN_WORKSPACE_NUMBER | CT_ASSIGN_OUTPUT);
454 struct sway_container *ws = NULL; 417 struct sway_container *ws = NULL;
455 for (int i = 0; i < criterias->length; ++i) { 418 for (int i = 0; i < criterias->length; ++i) {
456 struct criteria *criteria = criterias->items[i]; 419 struct criteria *criteria = criterias->items[i];
457 if (criteria->type == CT_ASSIGN_WORKSPACE) { 420 if (criteria->type == CT_ASSIGN_OUTPUT) {
458 ws = workspace_by_name(criteria->target); 421 struct sway_container *output = output_by_name(criteria->target);
422 if (output) {
423 ws = seat_get_active_child(seat, output);
424 break;
425 }
426 } else {
427 // CT_ASSIGN_WORKSPACE(_NUMBER)
428 ws = criteria->type == CT_ASSIGN_WORKSPACE_NUMBER ?
429 workspace_by_number(criteria->target) :
430 workspace_by_name(criteria->target);
431
459 if (!ws) { 432 if (!ws) {
460 if (strcasecmp(criteria->target, "back_and_forth") == 0) { 433 if (strcasecmp(criteria->target, "back_and_forth") == 0) {
461 if (prev_workspace_name) { 434 if (prev_workspace_name) {
@@ -466,13 +439,6 @@ static struct sway_container *select_workspace(struct sway_view *view) {
466 } 439 }
467 } 440 }
468 break; 441 break;
469 } else {
470 // CT_ASSIGN_OUTPUT
471 struct sway_container *output = output_by_name(criteria->target);
472 if (output) {
473 ws = seat_get_active_child(seat, output);
474 break;
475 }
476 } 442 }
477 } 443 }
478 list_free(criterias); 444 list_free(criterias);
@@ -528,7 +494,7 @@ static bool should_focus(struct sway_view *view) {
528 struct sway_container *parent = view->swayc->parent; 494 struct sway_container *parent = view->swayc->parent;
529 if (parent->type == C_WORKSPACE && prev_focus == parent) { 495 if (parent->type == C_WORKSPACE && prev_focus == parent) {
530 size_t num_children = parent->children->length + 496 size_t num_children = parent->children->length +
531 parent->sway_workspace->floating->children->length; 497 parent->sway_workspace->floating->length;
532 if (num_children == 1) { 498 if (num_children == 1) {
533 return true; 499 return true;
534 } 500 }
@@ -554,7 +520,7 @@ void view_map(struct sway_view *view, struct wlr_surface *wlr_surface) {
554 // If we're about to launch the view into the floating container, then 520 // If we're about to launch the view into the floating container, then
555 // launch it as a tiled view in the root of the workspace instead. 521 // launch it as a tiled view in the root of the workspace instead.
556 if (container_is_floating(target_sibling)) { 522 if (container_is_floating(target_sibling)) {
557 target_sibling = target_sibling->parent->parent; 523 target_sibling = target_sibling->parent;
558 } 524 }
559 525
560 view->swayc = container_view_create(target_sibling, view); 526 view->swayc = container_view_create(target_sibling, view);
@@ -564,9 +530,6 @@ void view_map(struct sway_view *view, struct wlr_surface *wlr_surface) {
564 &view->surface_new_subsurface); 530 &view->surface_new_subsurface);
565 view->surface_new_subsurface.notify = view_handle_surface_new_subsurface; 531 view->surface_new_subsurface.notify = view_handle_surface_new_subsurface;
566 532
567 wl_signal_add(&view->swayc->events.reparent, &view->container_reparent);
568 view->container_reparent.notify = view_handle_container_reparent;
569
570 if (view->impl->wants_floating && view->impl->wants_floating(view)) { 533 if (view->impl->wants_floating && view->impl->wants_floating(view)) {
571 view->border = config->floating_border; 534 view->border = config->floating_border;
572 view->border_thickness = config->floating_border_thickness; 535 view->border_thickness = config->floating_border_thickness;
@@ -584,15 +547,12 @@ void view_map(struct sway_view *view, struct wlr_surface *wlr_surface) {
584 view_update_title(view, false); 547 view_update_title(view, false);
585 container_notify_subtree_changed(view->swayc->parent); 548 container_notify_subtree_changed(view->swayc->parent);
586 view_execute_criteria(view); 549 view_execute_criteria(view);
587
588 view_handle_container_reparent(&view->container_reparent, NULL);
589} 550}
590 551
591void view_unmap(struct sway_view *view) { 552void view_unmap(struct sway_view *view) {
592 wl_signal_emit(&view->events.unmap, view); 553 wl_signal_emit(&view->events.unmap, view);
593 554
594 wl_list_remove(&view->surface_new_subsurface.link); 555 wl_list_remove(&view->surface_new_subsurface.link);
595 wl_list_remove(&view->container_reparent.link);
596 556
597 if (view->urgent_timer) { 557 if (view->urgent_timer) {
598 wl_event_source_remove(view->urgent_timer); 558 wl_event_source_remove(view->urgent_timer);
@@ -615,34 +575,16 @@ void view_unmap(struct sway_view *view) {
615 view->surface = NULL; 575 view->surface = NULL;
616} 576}
617 577
618void view_update_position(struct sway_view *view, double lx, double ly) {
619 if (view->x == lx && view->y == ly) {
620 return;
621 }
622 container_damage_whole(view->swayc);
623 view->x = lx;
624 view->y = ly;
625 view->swayc->current.view_x = lx;
626 view->swayc->current.view_y = ly;
627 if (container_is_floating(view->swayc)) {
628 container_set_geometry_from_floating_view(view->swayc);
629 }
630 container_damage_whole(view->swayc);
631}
632
633void view_update_size(struct sway_view *view, int width, int height) { 578void view_update_size(struct sway_view *view, int width, int height) {
634 if (view->width == width && view->height == height) { 579 if (!sway_assert(container_is_floating(view->swayc),
580 "Expected a floating container")) {
635 return; 581 return;
636 } 582 }
637 container_damage_whole(view->swayc);
638 view->width = width; 583 view->width = width;
639 view->height = height; 584 view->height = height;
640 view->swayc->current.view_width = width; 585 view->swayc->current.view_width = width;
641 view->swayc->current.view_height = height; 586 view->swayc->current.view_height = height;
642 if (container_is_floating(view->swayc)) { 587 container_set_geometry_from_floating_view(view->swayc);
643 container_set_geometry_from_floating_view(view->swayc);
644 }
645 container_damage_whole(view->swayc);
646} 588}
647 589
648static void view_subsurface_create(struct sway_view *view, 590static void view_subsurface_create(struct sway_view *view,
@@ -899,8 +841,8 @@ static bool find_by_mark_iterator(struct sway_container *con,
899} 841}
900 842
901struct sway_view *view_find_mark(char *mark) { 843struct sway_view *view_find_mark(char *mark) {
902 struct sway_container *container = container_find(&root_container, 844 struct sway_container *container = root_find_container(
903 find_by_mark_iterator, mark); 845 find_by_mark_iterator, mark);
904 if (!container) { 846 if (!container) {
905 return NULL; 847 return NULL;
906 } 848 }
@@ -908,7 +850,7 @@ struct sway_view *view_find_mark(char *mark) {
908} 850}
909 851
910bool view_find_and_unmark(char *mark) { 852bool view_find_and_unmark(char *mark) {
911 struct sway_container *container = container_find(&root_container, 853 struct sway_container *container = root_find_container(
912 find_by_mark_iterator, mark); 854 find_by_mark_iterator, mark);
913 if (!container) { 855 if (!container) {
914 return false; 856 return false;
@@ -952,7 +894,7 @@ void view_add_mark(struct sway_view *view, char *mark) {
952 894
953static void update_marks_texture(struct sway_view *view, 895static void update_marks_texture(struct sway_view *view,
954 struct wlr_texture **texture, struct border_colors *class) { 896 struct wlr_texture **texture, struct border_colors *class) {
955 struct sway_container *output = container_parent(view->swayc, C_OUTPUT); 897 struct sway_output *output = container_get_effective_output(view->swayc);
956 if (!output) { 898 if (!output) {
957 return; 899 return;
958 } 900 }
@@ -988,7 +930,7 @@ static void update_marks_texture(struct sway_view *view,
988 } 930 }
989 free(part); 931 free(part);
990 932
991 double scale = output->sway_output->wlr_output->scale; 933 double scale = output->wlr_output->scale;
992 int width = 0; 934 int width = 0;
993 int height = view->swayc->title_height * scale; 935 int height = view->swayc->title_height * scale;
994 936
@@ -1014,7 +956,7 @@ static void update_marks_texture(struct sway_view *view,
1014 unsigned char *data = cairo_image_surface_get_data(surface); 956 unsigned char *data = cairo_image_surface_get_data(surface);
1015 int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width); 957 int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width);
1016 struct wlr_renderer *renderer = wlr_backend_get_renderer( 958 struct wlr_renderer *renderer = wlr_backend_get_renderer(
1017 output->sway_output->wlr_output->backend); 959 output->wlr_output->backend);
1018 *texture = wlr_texture_from_pixels( 960 *texture = wlr_texture_from_pixels(
1019 renderer, WL_SHM_FORMAT_ARGB8888, stride, width, height, data); 961 renderer, WL_SHM_FORMAT_ARGB8888, stride, width, height, data);
1020 cairo_surface_destroy(surface); 962 cairo_surface_destroy(surface);
@@ -1061,7 +1003,7 @@ bool view_is_visible(struct sway_view *view) {
1061 // Check view isn't in a tabbed or stacked container on an inactive tab 1003 // Check view isn't in a tabbed or stacked container on an inactive tab
1062 struct sway_seat *seat = input_manager_current_seat(input_manager); 1004 struct sway_seat *seat = input_manager_current_seat(input_manager);
1063 struct sway_container *container = view->swayc; 1005 struct sway_container *container = view->swayc;
1064 while (container->type != C_WORKSPACE && container->layout != L_FLOATING) { 1006 while (container->type != C_WORKSPACE) {
1065 if (container->parent->layout == L_TABBED || 1007 if (container->parent->layout == L_TABBED ||
1066 container->parent->layout == L_STACKED) { 1008 container->parent->layout == L_STACKED) {
1067 if (seat_get_active_child(seat, container->parent) != container) { 1009 if (seat_get_active_child(seat, container->parent) != container) {
diff --git a/sway/tree/workspace.c b/sway/tree/workspace.c
index cd2a7a04..cf50ee09 100644
--- a/sway/tree/workspace.c
+++ b/sway/tree/workspace.c
@@ -67,26 +67,19 @@ struct sway_container *workspace_create(struct sway_container *output,
67 return NULL; 67 return NULL;
68 } 68 }
69 swayws->swayc = workspace; 69 swayws->swayc = workspace;
70 swayws->floating = container_create(C_CONTAINER); 70 swayws->floating = create_list();
71 swayws->floating->parent = swayws->swayc;
72 swayws->floating->layout = L_FLOATING;
73 swayws->output_priority = create_list(); 71 swayws->output_priority = create_list();
74 workspace->sway_workspace = swayws; 72 workspace->sway_workspace = swayws;
75 workspace_output_add_priority(workspace, output); 73 workspace_output_add_priority(workspace, output);
76 74
77 container_add_child(output, workspace); 75 container_add_child(output, workspace);
78 container_sort_workspaces(output); 76 output_sort_workspaces(output);
79 container_create_notify(workspace); 77 container_create_notify(workspace);
80 78
81 return workspace; 79 return workspace;
82} 80}
83 81
84char *prev_workspace_name = NULL; 82char *prev_workspace_name = NULL;
85struct workspace_by_number_data {
86 int len;
87 const char *cset;
88 const char *name;
89};
90 83
91void next_name_map(struct sway_container *ws, void *data) { 84void next_name_map(struct sway_container *ws, void *data) {
92 int *count = data; 85 int *count = data;
@@ -154,7 +147,7 @@ static void workspace_name_from_binding(const struct sway_binding * binding,
154 wlr_log(WLR_DEBUG, "Isolated name from workspace number: '%s'", _target); 147 wlr_log(WLR_DEBUG, "Isolated name from workspace number: '%s'", _target);
155 148
156 // Make sure the workspace number doesn't already exist 149 // Make sure the workspace number doesn't already exist
157 if (workspace_by_number(_target)) { 150 if (isdigit(_target[0]) && workspace_by_number(_target)) {
158 free(_target); 151 free(_target);
159 free(dup); 152 free(dup);
160 return; 153 return;
@@ -212,6 +205,7 @@ char *workspace_next_name(const char *output_name) {
212 && workspace_by_name(wso->workspace) == NULL) { 205 && workspace_by_name(wso->workspace) == NULL) {
213 free(target); 206 free(target);
214 target = strdup(wso->workspace); 207 target = strdup(wso->workspace);
208 break;
215 } 209 }
216 } 210 }
217 if (target != NULL) { 211 if (target != NULL) {
@@ -233,19 +227,18 @@ static bool _workspace_by_number(struct sway_container *view, void *data) {
233 if (view->type != C_WORKSPACE) { 227 if (view->type != C_WORKSPACE) {
234 return false; 228 return false;
235 } 229 }
236 struct workspace_by_number_data *wbnd = data; 230 char *name = data;
237 int a = strspn(view->name, wbnd->cset); 231 char *view_name = view->name;
238 return a == wbnd->len && strncmp(view->name, wbnd->name, a) == 0; 232 while (isdigit(*name)) {
233 if (*name++ != *view_name++) {
234 return false;
235 }
236 }
237 return !isdigit(*view_name);
239} 238}
240 239
241struct sway_container *workspace_by_number(const char* name) { 240struct sway_container *workspace_by_number(const char* name) {
242 struct workspace_by_number_data wbnd = {0, "1234567890", name}; 241 return root_find_workspace(_workspace_by_number, (void *) name);
243 wbnd.len = strspn(name, wbnd.cset);
244 if (wbnd.len <= 0) {
245 return NULL;
246 }
247 return container_find(&root_container,
248 _workspace_by_number, (void *) &wbnd);
249} 242}
250 243
251static bool _workspace_by_name(struct sway_container *view, void *data) { 244static bool _workspace_by_name(struct sway_container *view, void *data) {
@@ -258,7 +251,8 @@ struct sway_container *workspace_by_name(const char *name) {
258 struct sway_container *current_workspace = NULL, *current_output = NULL; 251 struct sway_container *current_workspace = NULL, *current_output = NULL;
259 struct sway_container *focus = seat_get_focus(seat); 252 struct sway_container *focus = seat_get_focus(seat);
260 if (focus) { 253 if (focus) {
261 current_workspace = container_parent(focus, C_WORKSPACE); 254 current_workspace = focus->type == C_WORKSPACE ?
255 focus : container_parent(focus, C_WORKSPACE);
262 current_output = container_parent(focus, C_OUTPUT); 256 current_output = container_parent(focus, C_OUTPUT);
263 } 257 }
264 258
@@ -273,11 +267,11 @@ struct sway_container *workspace_by_name(const char *name) {
273 } else if (strcmp(name, "current") == 0) { 267 } else if (strcmp(name, "current") == 0) {
274 return current_workspace; 268 return current_workspace;
275 } else if (strcasecmp(name, "back_and_forth") == 0) { 269 } else if (strcasecmp(name, "back_and_forth") == 0) {
276 return prev_workspace_name ? container_find(&root_container, 270 return prev_workspace_name ?
277 _workspace_by_name, (void *)prev_workspace_name) : NULL; 271 root_find_workspace(_workspace_by_name, (void*)prev_workspace_name)
272 : NULL;
278 } else { 273 } else {
279 return container_find(&root_container, _workspace_by_name, 274 return root_find_workspace(_workspace_by_name, (void*)name);
280 (void *)name);
281 } 275 }
282} 276}
283 277
@@ -397,17 +391,15 @@ bool workspace_switch(struct sway_container *workspace,
397 struct sway_container *next_output = workspace->parent; 391 struct sway_container *next_output = workspace->parent;
398 struct sway_container *next_output_prev_ws = 392 struct sway_container *next_output_prev_ws =
399 seat_get_active_child(seat, next_output); 393 seat_get_active_child(seat, next_output);
400 struct sway_container *floating = 394 list_t *floating = next_output_prev_ws->sway_workspace->floating;
401 next_output_prev_ws->sway_workspace->floating;
402 bool has_sticky = false; 395 bool has_sticky = false;
403 if (workspace != next_output_prev_ws) { 396 if (workspace != next_output_prev_ws) {
404 for (int i = 0; i < floating->children->length; ++i) { 397 for (int i = 0; i < floating->length; ++i) {
405 struct sway_container *floater = floating->children->items[i]; 398 struct sway_container *floater = floating->items[i];
406 if (floater->is_sticky) { 399 if (floater->is_sticky) {
407 has_sticky = true; 400 has_sticky = true;
408 container_remove_child(floater); 401 container_remove_child(floater);
409 container_add_child(workspace->sway_workspace->floating, 402 workspace_add_floating(workspace, floater);
410 floater);
411 if (floater == focus) { 403 if (floater == focus) {
412 seat_set_focus(seat, NULL); 404 seat_set_focus(seat, NULL);
413 seat_set_focus(seat, floater); 405 seat_set_focus(seat, floater);
@@ -460,9 +452,9 @@ bool workspace_is_empty(struct sway_container *ws) {
460 return false; 452 return false;
461 } 453 }
462 // Sticky views are not considered to be part of this workspace 454 // Sticky views are not considered to be part of this workspace
463 struct sway_container *floating = ws->sway_workspace->floating; 455 list_t *floating = ws->sway_workspace->floating;
464 for (int i = 0; i < floating->children->length; ++i) { 456 for (int i = 0; i < floating->length; ++i) {
465 struct sway_container *floater = floating->children->items[i]; 457 struct sway_container *floater = floating->items[i];
466 if (!floater->is_sticky) { 458 if (!floater->is_sticky) {
467 return false; 459 return false;
468 } 460 }
@@ -517,8 +509,7 @@ struct sway_container *workspace_output_get_highest_available(
517 continue; 509 continue;
518 } 510 }
519 511
520 struct sway_container *output = container_find(&root_container, 512 struct sway_container *output = root_find_output(_output_by_name, name);
521 _output_by_name, name);
522 if (output) { 513 if (output) {
523 return output; 514 return output;
524 } 515 }
@@ -527,8 +518,13 @@ struct sway_container *workspace_output_get_highest_available(
527 return NULL; 518 return NULL;
528} 519}
529 520
521static bool find_urgent_iterator(struct sway_container *con, void *data) {
522 return con->type == C_VIEW && view_is_urgent(con->sway_view);
523}
524
530void workspace_detect_urgent(struct sway_container *workspace) { 525void workspace_detect_urgent(struct sway_container *workspace) {
531 bool new_urgent = container_has_urgent_child(workspace); 526 bool new_urgent = (bool)workspace_find_container(workspace,
527 find_urgent_iterator, NULL);
532 528
533 if (workspace->sway_workspace->urgent != new_urgent) { 529 if (workspace->sway_workspace->urgent != new_urgent) {
534 workspace->sway_workspace->urgent = new_urgent; 530 workspace->sway_workspace->urgent = new_urgent;
@@ -536,3 +532,79 @@ void workspace_detect_urgent(struct sway_container *workspace) {
536 container_damage_whole(workspace); 532 container_damage_whole(workspace);
537 } 533 }
538} 534}
535
536void workspace_for_each_container(struct sway_container *ws,
537 void (*f)(struct sway_container *con, void *data), void *data) {
538 if (!sway_assert(ws->type == C_WORKSPACE, "Expected a workspace")) {
539 return;
540 }
541 // Tiling
542 for (int i = 0; i < ws->children->length; ++i) {
543 struct sway_container *container = ws->children->items[i];
544 f(container, data);
545 container_for_each_child(container, f, data);
546 }
547 // Floating
548 for (int i = 0; i < ws->sway_workspace->floating->length; ++i) {
549 struct sway_container *container =
550 ws->sway_workspace->floating->items[i];
551 f(container, data);
552 container_for_each_child(container, f, data);
553 }
554}
555
556struct sway_container *workspace_find_container(struct sway_container *ws,
557 bool (*test)(struct sway_container *con, void *data), void *data) {
558 if (!sway_assert(ws->type == C_WORKSPACE, "Expected a workspace")) {
559 return NULL;
560 }
561 struct sway_container *result = NULL;
562 // Tiling
563 for (int i = 0; i < ws->children->length; ++i) {
564 struct sway_container *child = ws->children->items[i];
565 if (test(child, data)) {
566 return child;
567 }
568 if ((result = container_find_child(child, test, data))) {
569 return result;
570 }
571 }
572 // Floating
573 for (int i = 0; i < ws->sway_workspace->floating->length; ++i) {
574 struct sway_container *child = ws->sway_workspace->floating->items[i];
575 if (test(child, data)) {
576 return child;
577 }
578 if ((result = container_find_child(child, test, data))) {
579 return result;
580 }
581 }
582 return NULL;
583}
584
585struct sway_container *workspace_wrap_children(struct sway_container *ws) {
586 struct sway_container *middle = container_create(C_CONTAINER);
587 middle->layout = ws->layout;
588 while (ws->children->length) {
589 struct sway_container *child = ws->children->items[0];
590 container_remove_child(child);
591 container_add_child(middle, child);
592 }
593 container_add_child(ws, middle);
594 return middle;
595}
596
597void workspace_add_floating(struct sway_container *workspace,
598 struct sway_container *con) {
599 if (!sway_assert(workspace->type == C_WORKSPACE, "Expected a workspace")) {
600 return;
601 }
602 if (!sway_assert(con->parent == NULL, "Expected an orphan container")) {
603 return;
604 }
605
606 list_add(workspace->sway_workspace->floating, con);
607 con->parent = workspace;
608 container_set_dirty(workspace);
609 container_set_dirty(con);
610}
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}