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