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