1 #define _POSIX_C_SOURCE 200809L
2 #include <assert.h>
3 #include <ctype.h>
4 #include <string.h>
5 #include <strings.h>
6 #include <wlr/types/wlr_output_damage.h>
7 #include "sway/ipc-server.h"
8 #include "sway/layers.h"
9 #include "sway/output.h"
10 #include "sway/tree/arrange.h"
11 #include "sway/tree/workspace.h"
12 #include "log.h"
13 #include "util.h"
14 
opposite_direction(enum wlr_direction d)15 enum wlr_direction opposite_direction(enum wlr_direction d) {
16 	switch (d) {
17 	case WLR_DIRECTION_UP:
18 		return WLR_DIRECTION_DOWN;
19 	case WLR_DIRECTION_DOWN:
20 		return WLR_DIRECTION_UP;
21 	case WLR_DIRECTION_RIGHT:
22 		return WLR_DIRECTION_LEFT;
23 	case WLR_DIRECTION_LEFT:
24 		return WLR_DIRECTION_RIGHT;
25 	}
26 	assert(false);
27 	return 0;
28 }
29 
restore_workspaces(struct sway_output * output)30 static void restore_workspaces(struct sway_output *output) {
31 	// Workspace output priority
32 	for (int i = 0; i < root->outputs->length; i++) {
33 		struct sway_output *other = root->outputs->items[i];
34 		if (other == output) {
35 			continue;
36 		}
37 
38 		for (int j = 0; j < other->workspaces->length; j++) {
39 			struct sway_workspace *ws = other->workspaces->items[j];
40 			struct sway_output *highest =
41 				workspace_output_get_highest_available(ws, NULL);
42 			if (highest == output) {
43 				workspace_detach(ws);
44 				output_add_workspace(output, ws);
45 				ipc_event_workspace(NULL, ws, "move");
46 				j--;
47 			}
48 		}
49 
50 		if (other->workspaces->length == 0) {
51 			char *next = workspace_next_name(other->wlr_output->name);
52 			struct sway_workspace *ws = workspace_create(other, next);
53 			free(next);
54 			ipc_event_workspace(NULL, ws, "init");
55 		}
56 	}
57 
58 	// Saved workspaces
59 	while (root->noop_output->workspaces->length) {
60 		struct sway_workspace *ws = root->noop_output->workspaces->items[0];
61 		workspace_detach(ws);
62 		output_add_workspace(output, ws);
63 
64 		// If the floater was made floating while on the NOOP output, its width
65 		// and height will be zero and it should be reinitialized as a floating
66 		// container to get the appropriate size and location. Additionally, if
67 		// the floater is wider or taller than the output or is completely
68 		// outside of the output's bounds, do the same as the output layout has
69 		// likely changed and the maximum size needs to be checked and the
70 		// floater re-centered
71 		for (int i = 0; i < ws->floating->length; i++) {
72 			struct sway_container *floater = ws->floating->items[i];
73 			if (floater->width == 0 || floater->height == 0 ||
74 					floater->width > output->width ||
75 					floater->height > output->height ||
76 					floater->x > output->lx + output->width ||
77 					floater->y > output->ly + output->height ||
78 					floater->x + floater->width < output->lx ||
79 					floater->y + floater->height < output->ly) {
80 				container_floating_resize_and_center(floater);
81 			}
82 		}
83 
84 		ipc_event_workspace(NULL, ws, "move");
85 	}
86 
87 	output_sort_workspaces(output);
88 }
89 
output_create(struct wlr_output * wlr_output)90 struct sway_output *output_create(struct wlr_output *wlr_output) {
91 	struct sway_output *output = calloc(1, sizeof(struct sway_output));
92 	node_init(&output->node, N_OUTPUT, output);
93 	output->wlr_output = wlr_output;
94 	wlr_output->data = output;
95 	output->detected_subpixel = wlr_output->subpixel;
96 	output->scale_filter = SCALE_FILTER_NEAREST;
97 
98 	wl_signal_init(&output->events.destroy);
99 
100 	wl_list_insert(&root->all_outputs, &output->link);
101 
102 	output->workspaces = create_list();
103 	output->current.workspaces = create_list();
104 
105 	size_t len = sizeof(output->layers) / sizeof(output->layers[0]);
106 	for (size_t i = 0; i < len; ++i) {
107 		wl_list_init(&output->layers[i]);
108 	}
109 
110 	return output;
111 }
112 
output_enable(struct sway_output * output)113 void output_enable(struct sway_output *output) {
114 	if (!sway_assert(!output->enabled, "output is already enabled")) {
115 		return;
116 	}
117 	struct wlr_output *wlr_output = output->wlr_output;
118 	output->enabled = true;
119 	list_add(root->outputs, output);
120 
121 	restore_workspaces(output);
122 
123 	struct sway_workspace *ws = NULL;
124 	if (!output->workspaces->length) {
125 		// Create workspace
126 		char *ws_name = workspace_next_name(wlr_output->name);
127 		sway_log(SWAY_DEBUG, "Creating default workspace %s", ws_name);
128 		ws = workspace_create(output, ws_name);
129 		// Set each seat's focus if not already set
130 		struct sway_seat *seat = NULL;
131 		wl_list_for_each(seat, &server.input->seats, link) {
132 			if (!seat->has_focus) {
133 				seat_set_focus_workspace(seat, ws);
134 			}
135 		}
136 		free(ws_name);
137 		ipc_event_workspace(NULL, ws, "init");
138 	}
139 
140 	if (ws && config->default_orientation == L_NONE) {
141 		// Since the output transformation and resolution could have changed
142 		// due to applying the output config, the previously set layout for the
143 		// created workspace may not be correct for `default_orientation auto`
144 		ws->layout = output_get_default_layout(output);
145 	}
146 
147 	input_manager_configure_xcursor();
148 
149 	wl_signal_emit(&root->events.new_node, &output->node);
150 
151 	arrange_layers(output);
152 	arrange_root();
153 }
154 
evacuate_sticky(struct sway_workspace * old_ws,struct sway_output * new_output)155 static void evacuate_sticky(struct sway_workspace *old_ws,
156 		struct sway_output *new_output) {
157 	struct sway_workspace *new_ws = output_get_active_workspace(new_output);
158 	if (!sway_assert(new_ws, "New output does not have a workspace")) {
159 		return;
160 	}
161 	while (old_ws->floating->length) {
162 		struct sway_container *sticky = old_ws->floating->items[0];
163 		container_detach(sticky);
164 		workspace_add_floating(new_ws, sticky);
165 		container_handle_fullscreen_reparent(sticky);
166 		container_floating_move_to_center(sticky);
167 		ipc_event_window(sticky, "move");
168 	}
169 	workspace_detect_urgent(new_ws);
170 }
171 
output_evacuate(struct sway_output * output)172 static void output_evacuate(struct sway_output *output) {
173 	if (!output->workspaces->length) {
174 		return;
175 	}
176 	struct sway_output *fallback_output = NULL;
177 	if (root->outputs->length > 1) {
178 		fallback_output = root->outputs->items[0];
179 		if (fallback_output == output) {
180 			fallback_output = root->outputs->items[1];
181 		}
182 	}
183 
184 	while (output->workspaces->length) {
185 		struct sway_workspace *workspace = output->workspaces->items[0];
186 
187 		workspace_detach(workspace);
188 
189 		struct sway_output *new_output =
190 			workspace_output_get_highest_available(workspace, output);
191 		if (!new_output) {
192 			new_output = fallback_output;
193 		}
194 		if (!new_output) {
195 			new_output = root->noop_output;
196 		}
197 
198 		if (workspace_is_empty(workspace)) {
199 			// If floating is not empty, there are sticky containers to move
200 			if (workspace->floating->length) {
201 				evacuate_sticky(workspace, new_output);
202 			}
203 			workspace_begin_destroy(workspace);
204 			continue;
205 		}
206 
207 		struct sway_workspace *new_output_ws =
208 			output_get_active_workspace(new_output);
209 
210 		workspace_output_add_priority(workspace, new_output);
211 		output_add_workspace(new_output, workspace);
212 		output_sort_workspaces(new_output);
213 		ipc_event_workspace(NULL, workspace, "move");
214 
215 		// If there is an old workspace (the noop output may not have one),
216 		// check to see if it is empty and should be destroyed.
217 		if (new_output_ws) {
218 			workspace_consider_destroy(new_output_ws);
219 		}
220 	}
221 }
222 
output_destroy(struct sway_output * output)223 void output_destroy(struct sway_output *output) {
224 	if (!sway_assert(output->node.destroying,
225 				"Tried to free output which wasn't marked as destroying")) {
226 		return;
227 	}
228 	if (!sway_assert(output->wlr_output == NULL,
229 				"Tried to free output which still had a wlr_output")) {
230 		return;
231 	}
232 	if (!sway_assert(output->node.ntxnrefs == 0, "Tried to free output "
233 				"which is still referenced by transactions")) {
234 		return;
235 	}
236 	list_free(output->workspaces);
237 	list_free(output->current.workspaces);
238 	wl_event_source_remove(output->repaint_timer);
239 	free(output);
240 }
241 
untrack_output(struct sway_container * con,void * data)242 static void untrack_output(struct sway_container *con, void *data) {
243 	struct sway_output *output = data;
244 	int index = list_find(con->outputs, output);
245 	if (index != -1) {
246 		list_del(con->outputs, index);
247 	}
248 }
249 
output_disable(struct sway_output * output)250 void output_disable(struct sway_output *output) {
251 	if (!sway_assert(output->enabled, "Expected an enabled output")) {
252 		return;
253 	}
254 	int index = list_find(root->outputs, output);
255 	if (!sway_assert(index >= 0, "Output not found in root node")) {
256 		return;
257 	}
258 
259 	sway_log(SWAY_DEBUG, "Disabling output '%s'", output->wlr_output->name);
260 	wl_signal_emit(&output->events.destroy, output);
261 
262 	output_evacuate(output);
263 
264 	root_for_each_container(untrack_output, output);
265 
266 	list_del(root->outputs, index);
267 
268 	output->enabled = false;
269 	output->current_mode = NULL;
270 
271 	arrange_root();
272 
273 	// Reconfigure all devices, since devices with map_to_output directives for
274 	// an output that goes offline should stop sending events as long as the
275 	// output remains offline.
276 	input_manager_configure_all_inputs();
277 }
278 
output_begin_destroy(struct sway_output * output)279 void output_begin_destroy(struct sway_output *output) {
280 	if (!sway_assert(!output->enabled, "Expected a disabled output")) {
281 		return;
282 	}
283 	sway_log(SWAY_DEBUG, "Destroying output '%s'", output->wlr_output->name);
284 
285 	output->node.destroying = true;
286 	node_set_dirty(&output->node);
287 
288 	wl_list_remove(&output->link);
289 	output->wlr_output->data = NULL;
290 	output->wlr_output = NULL;
291 }
292 
output_from_wlr_output(struct wlr_output * output)293 struct sway_output *output_from_wlr_output(struct wlr_output *output) {
294 	return output->data;
295 }
296 
output_get_in_direction(struct sway_output * reference,enum wlr_direction direction)297 struct sway_output *output_get_in_direction(struct sway_output *reference,
298 		enum wlr_direction direction) {
299 	if (!sway_assert(direction, "got invalid direction: %d", direction)) {
300 		return NULL;
301 	}
302 	struct wlr_box *output_box =
303 		wlr_output_layout_get_box(root->output_layout, reference->wlr_output);
304 	int lx = output_box->x + output_box->width / 2;
305 	int ly = output_box->y + output_box->height / 2;
306 	struct wlr_output *wlr_adjacent = wlr_output_layout_adjacent_output(
307 			root->output_layout, direction, reference->wlr_output, lx, ly);
308 	if (!wlr_adjacent) {
309 		return NULL;
310 	}
311 	return output_from_wlr_output(wlr_adjacent);
312 }
313 
output_add_workspace(struct sway_output * output,struct sway_workspace * workspace)314 void output_add_workspace(struct sway_output *output,
315 		struct sway_workspace *workspace) {
316 	if (workspace->output) {
317 		workspace_detach(workspace);
318 	}
319 	list_add(output->workspaces, workspace);
320 	workspace->output = output;
321 	node_set_dirty(&output->node);
322 	node_set_dirty(&workspace->node);
323 }
324 
output_for_each_workspace(struct sway_output * output,void (* f)(struct sway_workspace * ws,void * data),void * data)325 void output_for_each_workspace(struct sway_output *output,
326 		void (*f)(struct sway_workspace *ws, void *data), void *data) {
327 	for (int i = 0; i < output->workspaces->length; ++i) {
328 		struct sway_workspace *workspace = output->workspaces->items[i];
329 		f(workspace, data);
330 	}
331 }
332 
output_for_each_container(struct sway_output * output,void (* f)(struct sway_container * con,void * data),void * data)333 void output_for_each_container(struct sway_output *output,
334 		void (*f)(struct sway_container *con, void *data), void *data) {
335 	for (int i = 0; i < output->workspaces->length; ++i) {
336 		struct sway_workspace *workspace = output->workspaces->items[i];
337 		workspace_for_each_container(workspace, f, data);
338 	}
339 }
340 
output_find_workspace(struct sway_output * output,bool (* test)(struct sway_workspace * ws,void * data),void * data)341 struct sway_workspace *output_find_workspace(struct sway_output *output,
342 		bool (*test)(struct sway_workspace *ws, void *data), void *data) {
343 	for (int i = 0; i < output->workspaces->length; ++i) {
344 		struct sway_workspace *workspace = output->workspaces->items[i];
345 		if (test(workspace, data)) {
346 			return workspace;
347 		}
348 	}
349 	return NULL;
350 }
351 
output_find_container(struct sway_output * output,bool (* test)(struct sway_container * con,void * data),void * data)352 struct sway_container *output_find_container(struct sway_output *output,
353 		bool (*test)(struct sway_container *con, void *data), void *data) {
354 	struct sway_container *result = NULL;
355 	for (int i = 0; i < output->workspaces->length; ++i) {
356 		struct sway_workspace *workspace = output->workspaces->items[i];
357 		if ((result = workspace_find_container(workspace, test, data))) {
358 			return result;
359 		}
360 	}
361 	return NULL;
362 }
363 
sort_workspace_cmp_qsort(const void * _a,const void * _b)364 static int sort_workspace_cmp_qsort(const void *_a, const void *_b) {
365 	struct sway_workspace *a = *(void **)_a;
366 	struct sway_workspace *b = *(void **)_b;
367 
368 	if (isdigit(a->name[0]) && isdigit(b->name[0])) {
369 		int a_num = strtol(a->name, NULL, 10);
370 		int b_num = strtol(b->name, NULL, 10);
371 		return (a_num < b_num) ? -1 : (a_num > b_num);
372 	} else if (isdigit(a->name[0])) {
373 		return -1;
374 	} else if (isdigit(b->name[0])) {
375 		return 1;
376 	}
377 	return 0;
378 }
379 
output_sort_workspaces(struct sway_output * output)380 void output_sort_workspaces(struct sway_output *output) {
381 	list_stable_sort(output->workspaces, sort_workspace_cmp_qsort);
382 }
383 
output_get_box(struct sway_output * output,struct wlr_box * box)384 void output_get_box(struct sway_output *output, struct wlr_box *box) {
385 	box->x = output->lx;
386 	box->y = output->ly;
387 	box->width = output->width;
388 	box->height = output->height;
389 }
390 
output_get_default_layout(struct sway_output * output)391 enum sway_container_layout output_get_default_layout(
392 		struct sway_output *output) {
393 	if (config->default_layout != L_NONE) {
394 		return config->default_layout;
395 	}
396 	if (config->default_orientation != L_NONE) {
397 		return config->default_orientation;
398 	}
399 	if (output->height > output->width) {
400 		return L_VERT;
401 	}
402 	return L_HORIZ;
403 }
404