1 #define _POSIX_C_SOURCE 200809L
2 #include <stdbool.h>
3 #include <stdlib.h>
4 #include <string.h>
5 #include <wlr/types/wlr_output_layout.h>
6 #include "sway/desktop/transaction.h"
7 #include "sway/input/seat.h"
8 #include "sway/ipc-server.h"
9 #include "sway/output.h"
10 #include "sway/tree/arrange.h"
11 #include "sway/tree/container.h"
12 #include "sway/tree/root.h"
13 #include "sway/tree/workspace.h"
14 #include "list.h"
15 #include "log.h"
16 #include "util.h"
17
18 struct sway_root *root;
19
output_layout_handle_change(struct wl_listener * listener,void * data)20 static void output_layout_handle_change(struct wl_listener *listener,
21 void *data) {
22 arrange_root();
23 transaction_commit_dirty();
24 }
25
root_create(void)26 struct sway_root *root_create(void) {
27 struct sway_root *root = calloc(1, sizeof(struct sway_root));
28 if (!root) {
29 sway_log(SWAY_ERROR, "Unable to allocate sway_root");
30 return NULL;
31 }
32 node_init(&root->node, N_ROOT, root);
33 root->output_layout = wlr_output_layout_create();
34 wl_list_init(&root->all_outputs);
35 #if HAVE_XWAYLAND
36 wl_list_init(&root->xwayland_unmanaged);
37 #endif
38 wl_list_init(&root->drag_icons);
39 wl_signal_init(&root->events.new_node);
40 root->outputs = create_list();
41 root->scratchpad = create_list();
42
43 root->output_layout_change.notify = output_layout_handle_change;
44 wl_signal_add(&root->output_layout->events.change,
45 &root->output_layout_change);
46 return root;
47 }
48
root_destroy(struct sway_root * root)49 void root_destroy(struct sway_root *root) {
50 wl_list_remove(&root->output_layout_change.link);
51 list_free(root->scratchpad);
52 list_free(root->outputs);
53 wlr_output_layout_destroy(root->output_layout);
54 free(root);
55 }
56
root_scratchpad_add_container(struct sway_container * con,struct sway_workspace * ws)57 void root_scratchpad_add_container(struct sway_container *con, struct sway_workspace *ws) {
58 if (!sway_assert(!con->scratchpad, "Container is already in scratchpad")) {
59 return;
60 }
61
62 struct sway_container *parent = con->parent;
63 struct sway_workspace *workspace = con->workspace;
64
65 // Clear the fullscreen mode when sending to the scratchpad
66 if (con->fullscreen_mode != FULLSCREEN_NONE) {
67 container_fullscreen_disable(con);
68 }
69
70 // When a tiled window is sent to scratchpad, center and resize it.
71 if (!container_is_floating(con)) {
72 container_set_floating(con, true);
73 container_floating_set_default_size(con);
74 container_floating_move_to_center(con);
75 }
76
77 container_detach(con);
78 con->scratchpad = true;
79 list_add(root->scratchpad, con);
80 if (ws) {
81 workspace_add_floating(ws, con);
82 }
83
84 if (!ws) {
85 struct sway_seat *seat = input_manager_current_seat();
86 struct sway_node *new_focus = NULL;
87 if (parent) {
88 arrange_container(parent);
89 new_focus = seat_get_focus_inactive(seat, &parent->node);
90 }
91 if (!new_focus) {
92 arrange_workspace(workspace);
93 new_focus = seat_get_focus_inactive(seat, &workspace->node);
94 }
95 seat_set_focus(seat, new_focus);
96 }
97
98 ipc_event_window(con, "move");
99 }
100
root_scratchpad_remove_container(struct sway_container * con)101 void root_scratchpad_remove_container(struct sway_container *con) {
102 if (!sway_assert(con->scratchpad, "Container is not in scratchpad")) {
103 return;
104 }
105 con->scratchpad = false;
106 int index = list_find(root->scratchpad, con);
107 if (index != -1) {
108 list_del(root->scratchpad, index);
109 ipc_event_window(con, "move");
110 }
111 }
112
root_scratchpad_show(struct sway_container * con)113 void root_scratchpad_show(struct sway_container *con) {
114 struct sway_seat *seat = input_manager_current_seat();
115 struct sway_workspace *new_ws = seat_get_focused_workspace(seat);
116 if (!new_ws) {
117 sway_log(SWAY_DEBUG, "No focused workspace to show scratchpad on");
118 return;
119 }
120 struct sway_workspace *old_ws = con->workspace;
121
122 // If the current con or any of its parents are in fullscreen mode, we
123 // first need to disable it before showing the scratchpad con.
124 if (new_ws->fullscreen) {
125 container_fullscreen_disable(new_ws->fullscreen);
126 }
127 if (root->fullscreen_global) {
128 container_fullscreen_disable(root->fullscreen_global);
129 }
130
131 // Show the container
132 if (old_ws) {
133 container_detach(con);
134 workspace_consider_destroy(old_ws);
135 } else {
136 // Act on the ancestor of scratchpad hidden split containers
137 while (con->parent) {
138 con = con->parent;
139 }
140 }
141 workspace_add_floating(new_ws, con);
142
143 // Make sure the container's center point overlaps this workspace
144 double center_lx = con->x + con->width / 2;
145 double center_ly = con->y + con->height / 2;
146
147 struct wlr_box workspace_box;
148 workspace_get_box(new_ws, &workspace_box);
149 if (!wlr_box_contains_point(&workspace_box, center_lx, center_ly)) {
150 container_floating_resize_and_center(con);
151 }
152
153 arrange_workspace(new_ws);
154 seat_set_focus(seat, seat_get_focus_inactive(seat, &con->node));
155 }
156
disable_fullscreen(struct sway_container * con,void * data)157 static void disable_fullscreen(struct sway_container *con, void *data) {
158 if (con->fullscreen_mode != FULLSCREEN_NONE) {
159 container_fullscreen_disable(con);
160 }
161 }
162
root_scratchpad_hide(struct sway_container * con)163 void root_scratchpad_hide(struct sway_container *con) {
164 struct sway_seat *seat = input_manager_current_seat();
165 struct sway_node *focus = seat_get_focus_inactive(seat, &root->node);
166 struct sway_workspace *ws = con->workspace;
167
168 if (con->fullscreen_mode == FULLSCREEN_GLOBAL && !con->workspace) {
169 // If the container was made fullscreen global while in the scratchpad,
170 // it should be shown until fullscreen has been disabled
171 return;
172 }
173
174 disable_fullscreen(con, NULL);
175 container_for_each_child(con, disable_fullscreen, NULL);
176 container_detach(con);
177 arrange_workspace(ws);
178 if (&con->node == focus || node_has_ancestor(focus, &con->node)) {
179 seat_set_focus(seat, seat_get_focus_inactive(seat, &ws->node));
180 }
181 list_move_to_end(root->scratchpad, con);
182
183 ipc_event_window(con, "move");
184 }
185
186 struct pid_workspace {
187 pid_t pid;
188 char *workspace;
189 struct timespec time_added;
190
191 struct sway_output *output;
192 struct wl_listener output_destroy;
193
194 struct wl_list link;
195 };
196
197 static struct wl_list pid_workspaces;
198
199 /**
200 * Get the pid of a parent process given the pid of a child process.
201 *
202 * Returns the parent pid or NULL if the parent pid cannot be determined.
203 */
get_parent_pid(pid_t child)204 static pid_t get_parent_pid(pid_t child) {
205 pid_t parent = -1;
206 char file_name[100];
207 char *buffer = NULL;
208 const char *sep = " ";
209 FILE *stat = NULL;
210 size_t buf_size = 0;
211
212 sprintf(file_name, "/proc/%d/stat", child);
213
214 if ((stat = fopen(file_name, "r"))) {
215 if (getline(&buffer, &buf_size, stat) != -1) {
216 strtok(buffer, sep); // pid
217 strtok(NULL, sep); // executable name
218 strtok(NULL, sep); // state
219 char *token = strtok(NULL, sep); // parent pid
220 parent = strtol(token, NULL, 10);
221 }
222 free(buffer);
223 fclose(stat);
224 }
225
226 if (parent) {
227 return (parent == child) ? -1 : parent;
228 }
229
230 return -1;
231 }
232
pid_workspace_destroy(struct pid_workspace * pw)233 static void pid_workspace_destroy(struct pid_workspace *pw) {
234 wl_list_remove(&pw->output_destroy.link);
235 wl_list_remove(&pw->link);
236 free(pw->workspace);
237 free(pw);
238 }
239
root_workspace_for_pid(pid_t pid)240 struct sway_workspace *root_workspace_for_pid(pid_t pid) {
241 if (!pid_workspaces.prev && !pid_workspaces.next) {
242 wl_list_init(&pid_workspaces);
243 return NULL;
244 }
245
246 struct sway_workspace *ws = NULL;
247 struct pid_workspace *pw = NULL;
248
249 sway_log(SWAY_DEBUG, "Looking up workspace for pid %d", pid);
250
251 do {
252 struct pid_workspace *_pw = NULL;
253 wl_list_for_each(_pw, &pid_workspaces, link) {
254 if (pid == _pw->pid) {
255 pw = _pw;
256 sway_log(SWAY_DEBUG,
257 "found pid_workspace for pid %d, workspace %s",
258 pid, pw->workspace);
259 goto found;
260 }
261 }
262 pid = get_parent_pid(pid);
263 } while (pid > 1);
264 found:
265
266 if (pw && pw->workspace) {
267 ws = workspace_by_name(pw->workspace);
268
269 if (!ws) {
270 sway_log(SWAY_DEBUG,
271 "Creating workspace %s for pid %d because it disappeared",
272 pw->workspace, pid);
273 ws = workspace_create(pw->output, pw->workspace);
274 }
275
276 pid_workspace_destroy(pw);
277 }
278
279 return ws;
280 }
281
pw_handle_output_destroy(struct wl_listener * listener,void * data)282 static void pw_handle_output_destroy(struct wl_listener *listener, void *data) {
283 struct pid_workspace *pw = wl_container_of(listener, pw, output_destroy);
284 pw->output = NULL;
285 wl_list_remove(&pw->output_destroy.link);
286 wl_list_init(&pw->output_destroy.link);
287 }
288
root_record_workspace_pid(pid_t pid)289 void root_record_workspace_pid(pid_t pid) {
290 sway_log(SWAY_DEBUG, "Recording workspace for process %d", pid);
291 if (!pid_workspaces.prev && !pid_workspaces.next) {
292 wl_list_init(&pid_workspaces);
293 }
294
295 struct sway_seat *seat = input_manager_current_seat();
296 struct sway_workspace *ws = seat_get_focused_workspace(seat);
297 if (!ws) {
298 sway_log(SWAY_DEBUG, "Bailing out, no workspace");
299 return;
300 }
301 struct sway_output *output = ws->output;
302 if (!output) {
303 sway_log(SWAY_DEBUG, "Bailing out, no output");
304 return;
305 }
306
307 struct timespec now;
308 clock_gettime(CLOCK_MONOTONIC, &now);
309
310 // Remove expired entries
311 static const int timeout = 60;
312 struct pid_workspace *old, *_old;
313 wl_list_for_each_safe(old, _old, &pid_workspaces, link) {
314 if (now.tv_sec - old->time_added.tv_sec >= timeout) {
315 pid_workspace_destroy(old);
316 }
317 }
318
319 struct pid_workspace *pw = calloc(1, sizeof(struct pid_workspace));
320 pw->workspace = strdup(ws->name);
321 pw->output = output;
322 pw->pid = pid;
323 memcpy(&pw->time_added, &now, sizeof(struct timespec));
324 pw->output_destroy.notify = pw_handle_output_destroy;
325 wl_signal_add(&output->wlr_output->events.destroy, &pw->output_destroy);
326 wl_list_insert(&pid_workspaces, &pw->link);
327 }
328
root_remove_workspace_pid(pid_t pid)329 void root_remove_workspace_pid(pid_t pid) {
330 if (!pid_workspaces.prev || !pid_workspaces.next) {
331 return;
332 }
333
334 struct pid_workspace *pw, *tmp;
335 wl_list_for_each_safe(pw, tmp, &pid_workspaces, link) {
336 if (pid == pw->pid) {
337 pid_workspace_destroy(pw);
338 return;
339 }
340 }
341 }
342
root_for_each_workspace(void (* f)(struct sway_workspace * ws,void * data),void * data)343 void root_for_each_workspace(void (*f)(struct sway_workspace *ws, void *data),
344 void *data) {
345 for (int i = 0; i < root->outputs->length; ++i) {
346 struct sway_output *output = root->outputs->items[i];
347 output_for_each_workspace(output, f, data);
348 }
349 }
350
root_for_each_container(void (* f)(struct sway_container * con,void * data),void * data)351 void root_for_each_container(void (*f)(struct sway_container *con, void *data),
352 void *data) {
353 for (int i = 0; i < root->outputs->length; ++i) {
354 struct sway_output *output = root->outputs->items[i];
355 output_for_each_container(output, f, data);
356 }
357
358 // Scratchpad
359 for (int i = 0; i < root->scratchpad->length; ++i) {
360 struct sway_container *container = root->scratchpad->items[i];
361 if (container_is_scratchpad_hidden(container)) {
362 f(container, data);
363 container_for_each_child(container, f, data);
364 }
365 }
366
367 // Saved workspaces
368 for (int i = 0; i < root->noop_output->workspaces->length; ++i) {
369 struct sway_workspace *ws = root->noop_output->workspaces->items[i];
370 workspace_for_each_container(ws, f, data);
371 }
372 }
373
root_find_output(bool (* test)(struct sway_output * output,void * data),void * data)374 struct sway_output *root_find_output(
375 bool (*test)(struct sway_output *output, void *data), void *data) {
376 for (int i = 0; i < root->outputs->length; ++i) {
377 struct sway_output *output = root->outputs->items[i];
378 if (test(output, data)) {
379 return output;
380 }
381 }
382 return NULL;
383 }
384
root_find_workspace(bool (* test)(struct sway_workspace * ws,void * data),void * data)385 struct sway_workspace *root_find_workspace(
386 bool (*test)(struct sway_workspace *ws, void *data), void *data) {
387 struct sway_workspace *result = NULL;
388 for (int i = 0; i < root->outputs->length; ++i) {
389 struct sway_output *output = root->outputs->items[i];
390 if ((result = output_find_workspace(output, test, data))) {
391 return result;
392 }
393 }
394 return NULL;
395 }
396
root_find_container(bool (* test)(struct sway_container * con,void * data),void * data)397 struct sway_container *root_find_container(
398 bool (*test)(struct sway_container *con, void *data), void *data) {
399 struct sway_container *result = NULL;
400 for (int i = 0; i < root->outputs->length; ++i) {
401 struct sway_output *output = root->outputs->items[i];
402 if ((result = output_find_container(output, test, data))) {
403 return result;
404 }
405 }
406
407 // Scratchpad
408 for (int i = 0; i < root->scratchpad->length; ++i) {
409 struct sway_container *container = root->scratchpad->items[i];
410 if (container_is_scratchpad_hidden(container)) {
411 if (test(container, data)) {
412 return container;
413 }
414 if ((result = container_find_child(container, test, data))) {
415 return result;
416 }
417 }
418 }
419
420 // Saved workspaces
421 for (int i = 0; i < root->noop_output->workspaces->length; ++i) {
422 struct sway_workspace *ws = root->noop_output->workspaces->items[i];
423 if ((result = workspace_find_container(ws, test, data))) {
424 return result;
425 }
426 }
427
428 return NULL;
429 }
430
root_get_box(struct sway_root * root,struct wlr_box * box)431 void root_get_box(struct sway_root *root, struct wlr_box *box) {
432 box->x = root->x;
433 box->y = root->y;
434 box->width = root->width;
435 box->height = root->height;
436 }
437
root_rename_pid_workspaces(const char * old_name,const char * new_name)438 void root_rename_pid_workspaces(const char *old_name, const char *new_name) {
439 if (!pid_workspaces.prev && !pid_workspaces.next) {
440 wl_list_init(&pid_workspaces);
441 }
442
443 struct pid_workspace *pw = NULL;
444 wl_list_for_each(pw, &pid_workspaces, link) {
445 if (strcmp(pw->workspace, old_name) == 0) {
446 free(pw->workspace);
447 pw->workspace = strdup(new_name);
448 }
449 }
450 }
451