1 #define _POSIX_C_SOURCE 200809L
2 #include <ctype.h>
3 #include <stdbool.h>
4 #include <string.h>
5 #include <strings.h>
6 #include <wlr/types/wlr_cursor.h>
7 #include <wlr/types/wlr_output.h>
8 #include <wlr/types/wlr_output_layout.h>
9 #include "sway/commands.h"
10 #include "sway/input/cursor.h"
11 #include "sway/input/seat.h"
12 #include "sway/ipc-server.h"
13 #include "sway/output.h"
14 #include "sway/tree/arrange.h"
15 #include "sway/tree/container.h"
16 #include "sway/tree/root.h"
17 #include "sway/tree/workspace.h"
18 #include "stringop.h"
19 #include "list.h"
20 #include "log.h"
21 #include "util.h"
22 
23 static const char expected_syntax[] =
24 	"Expected 'move <left|right|up|down> <[px] px>' or "
25 	"'move [--no-auto-back-and-forth] <container|window> [to] workspace <name>' or "
26 	"'move <container|window|workspace> [to] output <name|direction>' or "
27 	"'move <container|window> [to] mark <mark>'";
28 
output_in_direction(const char * direction_string,struct sway_output * reference,int ref_lx,int ref_ly)29 static struct sway_output *output_in_direction(const char *direction_string,
30 		struct sway_output *reference, int ref_lx, int ref_ly) {
31 	if (strcasecmp(direction_string, "current") == 0) {
32 		struct sway_workspace *active_ws =
33 			seat_get_focused_workspace(config->handler_context.seat);
34 		if (!active_ws) {
35 			return NULL;
36 		}
37 		return active_ws->output;
38 	}
39 
40 	struct {
41 		char *name;
42 		enum wlr_direction direction;
43 	} names[] = {
44 		{ "up", WLR_DIRECTION_UP },
45 		{ "down", WLR_DIRECTION_DOWN },
46 		{ "left", WLR_DIRECTION_LEFT },
47 		{ "right", WLR_DIRECTION_RIGHT },
48 	};
49 
50 	enum wlr_direction direction = 0;
51 
52 	for (size_t i = 0; i < sizeof(names) / sizeof(names[0]); ++i) {
53 		if (strcasecmp(names[i].name, direction_string) == 0) {
54 			direction = names[i].direction;
55 			break;
56 		}
57 	}
58 
59 	if (reference && direction) {
60 		struct wlr_output *target = wlr_output_layout_adjacent_output(
61 				root->output_layout, direction, reference->wlr_output,
62 				ref_lx, ref_ly);
63 
64 		if (!target) {
65 			target = wlr_output_layout_farthest_output(
66 					root->output_layout, opposite_direction(direction),
67 					reference->wlr_output, ref_lx, ref_ly);
68 		}
69 
70 		if (target) {
71 			return target->data;
72 		}
73 	}
74 
75 	return output_by_name_or_id(direction_string);
76 }
77 
is_parallel(enum sway_container_layout layout,enum wlr_direction dir)78 static bool is_parallel(enum sway_container_layout layout,
79 		enum wlr_direction dir) {
80 	switch (layout) {
81 	case L_TABBED:
82 	case L_HORIZ:
83 		return dir == WLR_DIRECTION_LEFT || dir == WLR_DIRECTION_RIGHT;
84 	case L_STACKED:
85 	case L_VERT:
86 		return dir == WLR_DIRECTION_UP || dir == WLR_DIRECTION_DOWN;
87 	default:
88 		return false;
89 	}
90 }
91 
92 /**
93  * Ensures all seats focus the fullscreen container if needed.
94  */
workspace_focus_fullscreen(struct sway_workspace * workspace)95 static void workspace_focus_fullscreen(struct sway_workspace *workspace) {
96 	if (!workspace->fullscreen) {
97 		return;
98 	}
99 	struct sway_seat *seat;
100 	struct sway_workspace *focus_ws;
101 	wl_list_for_each(seat, &server.input->seats, link) {
102 		focus_ws = seat_get_focused_workspace(seat);
103 		if (focus_ws == workspace) {
104 			struct sway_node *new_focus =
105 				seat_get_focus_inactive(seat, &workspace->fullscreen->node);
106 			seat_set_raw_focus(seat, new_focus);
107 		}
108 	}
109 }
110 
container_move_to_container_from_direction(struct sway_container * container,struct sway_container * destination,enum wlr_direction move_dir)111 static void container_move_to_container_from_direction(
112 		struct sway_container *container, struct sway_container *destination,
113 		enum wlr_direction move_dir) {
114 	if (destination->view) {
115 		if (destination->parent == container->parent &&
116 				destination->workspace == container->workspace) {
117 			sway_log(SWAY_DEBUG, "Swapping siblings");
118 			list_t *siblings = container_get_siblings(container);
119 			int container_index = list_find(siblings, container);
120 			int destination_index = list_find(siblings, destination);
121 			list_swap(siblings, container_index, destination_index);
122 		} else {
123 			sway_log(SWAY_DEBUG, "Promoting to sibling of cousin");
124 			int offset =
125 				move_dir == WLR_DIRECTION_LEFT || move_dir == WLR_DIRECTION_UP;
126 			int index = container_sibling_index(destination) + offset;
127 			if (destination->parent) {
128 				container_insert_child(destination->parent, container, index);
129 			} else {
130 				workspace_insert_tiling(destination->workspace,
131 						container, index);
132 			}
133 			container->width = container->height = 0;
134 			container->width_fraction = container->height_fraction = 0;
135 		}
136 		return;
137 	}
138 
139 	if (is_parallel(destination->layout, move_dir)) {
140 		sway_log(SWAY_DEBUG, "Reparenting container (parallel)");
141 		int index =
142 			move_dir == WLR_DIRECTION_RIGHT || move_dir == WLR_DIRECTION_DOWN ?
143 			0 : destination->children->length;
144 		container_insert_child(destination, container, index);
145 		container->width = container->height = 0;
146 		container->width_fraction = container->height_fraction = 0;
147 		return;
148 	}
149 
150 	sway_log(SWAY_DEBUG, "Reparenting container (perpendicular)");
151 	struct sway_node *focus_inactive = seat_get_active_tiling_child(
152 			config->handler_context.seat, &destination->node);
153 	if (!focus_inactive || focus_inactive == &destination->node) {
154 		// The container has no children
155 		container_add_child(destination, container);
156 		return;
157 	}
158 
159 	// Try again but with the child
160 	container_move_to_container_from_direction(container,
161 			focus_inactive->sway_container, move_dir);
162 }
163 
container_move_to_workspace_from_direction(struct sway_container * container,struct sway_workspace * workspace,enum wlr_direction move_dir)164 static void container_move_to_workspace_from_direction(
165 		struct sway_container *container, struct sway_workspace *workspace,
166 		enum wlr_direction move_dir) {
167 	container->width = container->height = 0;
168 	container->width_fraction = container->height_fraction = 0;
169 
170 	if (is_parallel(workspace->layout, move_dir)) {
171 		sway_log(SWAY_DEBUG, "Reparenting container (parallel)");
172 		int index =
173 			move_dir == WLR_DIRECTION_RIGHT || move_dir == WLR_DIRECTION_DOWN ?
174 			0 : workspace->tiling->length;
175 		workspace_insert_tiling(workspace, container, index);
176 		return;
177 	}
178 
179 	sway_log(SWAY_DEBUG, "Reparenting container (perpendicular)");
180 	struct sway_container *focus_inactive = seat_get_focus_inactive_tiling(
181 			config->handler_context.seat, workspace);
182 	if (!focus_inactive) {
183 		// The workspace has no tiling children
184 		workspace_add_tiling(workspace, container);
185 		return;
186 	}
187 	while (focus_inactive->parent) {
188 		focus_inactive = focus_inactive->parent;
189 	}
190 	container_move_to_container_from_direction(container, focus_inactive,
191 			move_dir);
192 }
193 
container_move_to_workspace(struct sway_container * container,struct sway_workspace * workspace)194 static void container_move_to_workspace(struct sway_container *container,
195 		struct sway_workspace *workspace) {
196 	if (container->workspace == workspace) {
197 		return;
198 	}
199 	struct sway_workspace *old_workspace = container->workspace;
200 	if (container_is_floating(container)) {
201 		struct sway_output *old_output = container->workspace->output;
202 		container_detach(container);
203 		workspace_add_floating(workspace, container);
204 		container_handle_fullscreen_reparent(container);
205 		// If changing output, center it within the workspace
206 		if (old_output != workspace->output && !container->fullscreen_mode) {
207 			container_floating_move_to_center(container);
208 		}
209 	} else {
210 		container_detach(container);
211 		if (workspace_is_empty(workspace) && container->children) {
212 			workspace_unwrap_children(workspace, container);
213 		} else {
214 			container->width = container->height = 0;
215 			container->width_fraction = container->height_fraction = 0;
216 			workspace_add_tiling(workspace, container);
217 		}
218 		container_update_representation(container);
219 	}
220 	if (container->view) {
221 		ipc_event_window(container, "move");
222 	}
223 	workspace_detect_urgent(old_workspace);
224 	workspace_detect_urgent(workspace);
225 	workspace_focus_fullscreen(workspace);
226 }
227 
container_move_to_container(struct sway_container * container,struct sway_container * destination)228 static void container_move_to_container(struct sway_container *container,
229 		struct sway_container *destination) {
230 	if (container == destination
231 			|| container_has_ancestor(container, destination)
232 			|| container_has_ancestor(destination, container)) {
233 		return;
234 	}
235 	if (container_is_floating(container)) {
236 		container_move_to_workspace(container, destination->workspace);
237 		return;
238 	}
239 	struct sway_workspace *old_workspace = container->workspace;
240 
241 	container_detach(container);
242 	container->width = container->height = 0;
243 	container->width_fraction = container->height_fraction = 0;
244 
245 	if (destination->view) {
246 		container_add_sibling(destination, container, 1);
247 	} else {
248 		container_add_child(destination, container);
249 	}
250 
251 	if (container->view) {
252 		ipc_event_window(container, "move");
253 	}
254 
255 	if (destination->workspace) {
256 		workspace_focus_fullscreen(destination->workspace);
257 		workspace_detect_urgent(destination->workspace);
258 	}
259 
260 	if (old_workspace && old_workspace != destination->workspace) {
261 		workspace_detect_urgent(old_workspace);
262 	}
263 }
264 
265 /* Takes one child, sets it aside, wraps the rest of the children in a new
266  * container, switches the layout of the workspace, and drops the child back in.
267  * In other words, rejigger it. */
workspace_rejigger(struct sway_workspace * ws,struct sway_container * child,enum wlr_direction move_dir)268 static void workspace_rejigger(struct sway_workspace *ws,
269 		struct sway_container *child, enum wlr_direction move_dir) {
270 	if (!child->parent && ws->tiling->length == 1) {
271 		ws->layout =
272 			move_dir == WLR_DIRECTION_LEFT || move_dir == WLR_DIRECTION_RIGHT ?
273 			L_HORIZ : L_VERT;
274 		workspace_update_representation(ws);
275 		return;
276 	}
277 	container_detach(child);
278 	workspace_wrap_children(ws);
279 
280 	int index =
281 		move_dir == WLR_DIRECTION_LEFT || move_dir == WLR_DIRECTION_UP ? 0 : 1;
282 	workspace_insert_tiling(ws, child, index);
283 	ws->layout =
284 		move_dir == WLR_DIRECTION_LEFT || move_dir == WLR_DIRECTION_RIGHT ?
285 		L_HORIZ : L_VERT;
286 	workspace_update_representation(ws);
287 	child->width = child->height = 0;
288 }
289 
290 // Returns true if moved
container_move_in_direction(struct sway_container * container,enum wlr_direction move_dir)291 static bool container_move_in_direction(struct sway_container *container,
292 		enum wlr_direction move_dir) {
293 	// If moving a fullscreen view, only consider outputs
294 	if (container->fullscreen_mode == FULLSCREEN_WORKSPACE) {
295 		struct sway_output *new_output =
296 			output_get_in_direction(container->workspace->output, move_dir);
297 		if (!new_output) {
298 			return false;
299 		}
300 		struct sway_workspace *ws = output_get_active_workspace(new_output);
301 		if (!sway_assert(ws, "Expected output to have a workspace")) {
302 			return false;
303 		}
304 		container_move_to_workspace(container, ws);
305 		return true;
306 	}
307 	if (container->fullscreen_mode == FULLSCREEN_GLOBAL) {
308 		return false;
309 	}
310 
311 	// If container is in a split container by itself, move out of the split
312 	if (container->parent) {
313 		struct sway_container *new_parent =
314 			container_flatten(container->parent);
315 		if (new_parent != container->parent) {
316 			return true;
317 		}
318 	}
319 
320 	// Look for a suitable *container* sibling or parent.
321 	// The below loop stops once we hit the workspace because current->parent
322 	// is NULL for the topmost containers in a workspace.
323 	struct sway_container *current = container;
324 	int offs =
325 		move_dir == WLR_DIRECTION_LEFT || move_dir == WLR_DIRECTION_UP ? -1 : 1;
326 
327 	while (current) {
328 		list_t *siblings = container_get_siblings(current);
329 		if (siblings) {
330 			enum sway_container_layout layout = container_parent_layout(current);
331 			int index = list_find(siblings, current);
332 			int desired = index + offs;
333 
334 			// Don't allow containers to move out of their
335 			// fullscreen or floating parent
336 			if (current->fullscreen_mode || container_is_floating(current)) {
337 				return false;
338 			}
339 
340 			if (is_parallel(layout, move_dir)) {
341 				if (desired == -1 || desired == siblings->length) {
342 					if (current->parent == container->parent) {
343 						current = current->parent;
344 						continue;
345 					} else {
346 						// Reparenting
347 						if (current->parent) {
348 							container_insert_child(current->parent, container,
349 									index + (offs < 0 ? 0 : 1));
350 						} else {
351 							struct sway_workspace *ws = current->workspace;
352 							workspace_insert_tiling(ws,
353 								container_split(container,
354 									output_get_default_layout(ws->output)),
355 								index + (offs < 0 ? 0 : 1));
356 						}
357 						return true;
358 					}
359 				} else {
360 					// Container can move within its siblings
361 					container_move_to_container_from_direction(container,
362 							siblings->items[desired], move_dir);
363 					return true;
364 				}
365 			}
366 		}
367 
368 		current = current->parent;
369 	}
370 
371 	// Maybe rejigger the workspace
372 	struct sway_workspace *ws = container->workspace;
373 	if (ws) {
374 		if (!is_parallel(ws->layout, move_dir)) {
375 			workspace_rejigger(ws, container, move_dir);
376 			return true;
377 		} else if (ws->layout == L_TABBED || ws->layout == L_STACKED) {
378 			workspace_rejigger(ws, container, move_dir);
379 			return true;
380 		}
381 
382 		// Try adjacent output
383 		struct sway_output *output =
384 			output_get_in_direction(container->workspace->output, move_dir);
385 		if (output) {
386 			struct sway_workspace *ws = output_get_active_workspace(output);
387 			if (!sway_assert(ws, "Expected output to have a workspace")) {
388 				return false;
389 			}
390 			container_move_to_workspace_from_direction(container, ws, move_dir);
391 			return true;
392 		}
393 		sway_log(SWAY_DEBUG, "Hit edge of output, nowhere else to go");
394 	}
395 	return false;
396 }
397 
398 static struct cmd_results *cmd_move_to_scratchpad(void);
399 
cmd_move_container(bool no_auto_back_and_forth,int argc,char ** argv)400 static struct cmd_results *cmd_move_container(bool no_auto_back_and_forth,
401 		int argc, char **argv) {
402 	struct cmd_results *error = NULL;
403 	if ((error = checkarg(argc, "move container/window",
404 				EXPECTED_AT_LEAST, 2))) {
405 		return error;
406 	}
407 
408 	struct sway_node *node = config->handler_context.node;
409 	struct sway_workspace *workspace = config->handler_context.workspace;
410 	struct sway_container *container = config->handler_context.container;
411 	if (node->type == N_WORKSPACE) {
412 		if (workspace->tiling->length == 0) {
413 			return cmd_results_new(CMD_FAILURE,
414 					"Can't move an empty workspace");
415 		}
416 		container = workspace_wrap_children(workspace);
417 	}
418 
419 	if (container->fullscreen_mode == FULLSCREEN_GLOBAL) {
420 		return cmd_results_new(CMD_FAILURE,
421 				"Can't move fullscreen global container");
422 	}
423 
424 	struct sway_seat *seat = config->handler_context.seat;
425 	struct sway_container *old_parent = container->parent;
426 	struct sway_workspace *old_ws = container->workspace;
427 	struct sway_output *old_output = old_ws ? old_ws->output : NULL;
428 	struct sway_node *destination = NULL;
429 
430 	// determine destination
431 	if (strcasecmp(argv[0], "workspace") == 0) {
432 		// move container to workspace x
433 		struct sway_workspace *ws = NULL;
434 		char *ws_name = NULL;
435 		if (strcasecmp(argv[1], "next") == 0 ||
436 				strcasecmp(argv[1], "prev") == 0 ||
437 				strcasecmp(argv[1], "next_on_output") == 0 ||
438 				strcasecmp(argv[1], "prev_on_output") == 0 ||
439 				strcasecmp(argv[1], "current") == 0) {
440 			ws = workspace_by_name(argv[1]);
441 		} else if (strcasecmp(argv[1], "back_and_forth") == 0) {
442 			if (!(ws = workspace_by_name(argv[1]))) {
443 				if (seat->prev_workspace_name) {
444 					ws_name = strdup(seat->prev_workspace_name);
445 				} else {
446 					return cmd_results_new(CMD_FAILURE,
447 							"No workspace was previously active.");
448 				}
449 			}
450 		} else {
451 			if (strcasecmp(argv[1], "number") == 0) {
452 				// move [window|container] [to] "workspace number x"
453 				if (argc < 3) {
454 					return cmd_results_new(CMD_INVALID, expected_syntax);
455 				}
456 				if (!isdigit(argv[2][0])) {
457 					return cmd_results_new(CMD_INVALID,
458 							"Invalid workspace number '%s'", argv[2]);
459 				}
460 				ws_name = join_args(argv + 2, argc - 2);
461 				ws = workspace_by_number(ws_name);
462 			} else {
463 				ws_name = join_args(argv + 1, argc - 1);
464 				ws = workspace_by_name(ws_name);
465 			}
466 
467 			if (!no_auto_back_and_forth && config->auto_back_and_forth &&
468 					seat->prev_workspace_name) {
469 				// auto back and forth move
470 				if (old_ws && old_ws->name &&
471 						strcmp(old_ws->name, ws_name) == 0) {
472 					// if target workspace is the current one
473 					free(ws_name);
474 					ws_name = strdup(seat->prev_workspace_name);
475 					ws = workspace_by_name(ws_name);
476 				}
477 			}
478 		}
479 		if (!ws) {
480 			// We have to create the workspace, but if the container is
481 			// sticky and the workspace is going to be created on the same
482 			// output, we'll bail out first.
483 			if (container->is_sticky && container_is_floating_or_child(container)) {
484 				struct sway_output *new_output =
485 					workspace_get_initial_output(ws_name);
486 				if (old_output == new_output) {
487 					free(ws_name);
488 					return cmd_results_new(CMD_FAILURE,
489 							"Can't move sticky container to another workspace "
490 							"on the same output");
491 				}
492 			}
493 			ws = workspace_create(NULL, ws_name);
494 		}
495 		free(ws_name);
496 		struct sway_container *dst = seat_get_focus_inactive_tiling(seat, ws);
497 		destination = dst ? &dst->node : &ws->node;
498 	} else if (strcasecmp(argv[0], "output") == 0) {
499 		struct sway_output *new_output = output_in_direction(argv[1],
500 				old_output, container->x, container->y);
501 		if (!new_output) {
502 			return cmd_results_new(CMD_FAILURE,
503 				"Can't find output with name/direction '%s'", argv[1]);
504 		}
505 		destination = seat_get_focus_inactive(seat, &new_output->node);
506 	} else if (strcasecmp(argv[0], "mark") == 0) {
507 		struct sway_container *dest_con = container_find_mark(argv[1]);
508 		if (dest_con == NULL) {
509 			return cmd_results_new(CMD_FAILURE,
510 					"Mark '%s' not found", argv[1]);
511 		}
512 		destination = &dest_con->node;
513 	} else {
514 		return cmd_results_new(CMD_INVALID, expected_syntax);
515 	}
516 
517 	if (destination->type == N_CONTAINER &&
518 			container_is_scratchpad_hidden(destination->sway_container)) {
519 		return cmd_move_to_scratchpad();
520 	}
521 
522 	if (container->is_sticky && container_is_floating_or_child(container) &&
523 			old_output && node_has_ancestor(destination, &old_output->node)) {
524 		return cmd_results_new(CMD_FAILURE, "Can't move sticky "
525 				"container to another workspace on the same output");
526 	}
527 
528 	struct sway_output *new_output = node_get_output(destination);
529 	struct sway_workspace *new_output_last_ws = NULL;
530 	if (new_output && old_output != new_output) {
531 		new_output_last_ws = output_get_active_workspace(new_output);
532 	}
533 
534 	// save focus, in case it needs to be restored
535 	struct sway_node *focus = seat_get_focus(seat);
536 
537 	// move container
538 	if (container->scratchpad) {
539 		root_scratchpad_show(container);
540 	}
541 	switch (destination->type) {
542 	case N_WORKSPACE:
543 		container_move_to_workspace(container, destination->sway_workspace);
544 		break;
545 	case N_OUTPUT: {
546 			struct sway_output *output = destination->sway_output;
547 			struct sway_workspace *ws = output_get_active_workspace(output);
548 			if (!sway_assert(ws, "Expected output to have a workspace")) {
549 				return cmd_results_new(CMD_FAILURE,
550 						"Expected output to have a workspace");
551 			}
552 			container_move_to_workspace(container, ws);
553 		}
554 		break;
555 	case N_CONTAINER:
556 		container_move_to_container(container, destination->sway_container);
557 		break;
558 	case N_ROOT:
559 		break;
560 	}
561 
562 	// restore focus on destination output back to its last active workspace
563 	struct sway_workspace *new_workspace = new_output ?
564 		output_get_active_workspace(new_output) : NULL;
565 	if (new_output &&
566 			!sway_assert(new_workspace, "Expected output to have a workspace")) {
567 		return cmd_results_new(CMD_FAILURE,
568 				"Expected output to have a workspace");
569 	}
570 
571 	if (new_output_last_ws && new_output_last_ws != new_workspace) {
572 		struct sway_node *new_output_last_focus =
573 			seat_get_focus_inactive(seat, &new_output_last_ws->node);
574 		seat_set_raw_focus(seat, new_output_last_focus);
575 	}
576 
577 	// restore focus
578 	if (focus == &container->node) {
579 		focus = NULL;
580 		if (old_parent) {
581 			focus = seat_get_focus_inactive(seat, &old_parent->node);
582 		}
583 		if (!focus && old_ws) {
584 			focus = seat_get_focus_inactive(seat, &old_ws->node);
585 		}
586 	}
587 	seat_set_focus(seat, focus);
588 
589 	// clean-up, destroying parents if the container was the last child
590 	if (old_parent) {
591 		container_reap_empty(old_parent);
592 	} else if (old_ws) {
593 		workspace_consider_destroy(old_ws);
594 	}
595 
596 	// arrange windows
597 	if (root->fullscreen_global) {
598 		arrange_root();
599 	} else {
600 		if (old_ws && !old_ws->node.destroying) {
601 			arrange_workspace(old_ws);
602 		}
603 		arrange_node(node_get_parent(destination));
604 	}
605 
606 	return cmd_results_new(CMD_SUCCESS, NULL);
607 }
608 
workspace_move_to_output(struct sway_workspace * workspace,struct sway_output * output)609 static void workspace_move_to_output(struct sway_workspace *workspace,
610 		struct sway_output *output) {
611 	if (workspace->output == output) {
612 		return;
613 	}
614 	struct sway_output *old_output = workspace->output;
615 	workspace_detach(workspace);
616 	struct sway_workspace *new_output_old_ws =
617 		output_get_active_workspace(output);
618 	if (!sway_assert(new_output_old_ws, "Expected output to have a workspace")) {
619 		return;
620 	}
621 
622 	output_add_workspace(output, workspace);
623 
624 	// If moving the last workspace from the old output, create a new workspace
625 	// on the old output
626 	struct sway_seat *seat = config->handler_context.seat;
627 	if (old_output->workspaces->length == 0) {
628 		char *ws_name = workspace_next_name(old_output->wlr_output->name);
629 		struct sway_workspace *ws = workspace_create(old_output, ws_name);
630 		free(ws_name);
631 		seat_set_raw_focus(seat, &ws->node);
632 	}
633 
634 	workspace_consider_destroy(new_output_old_ws);
635 
636 	output_sort_workspaces(output);
637 	struct sway_node *focus = seat_get_focus_inactive(seat, &workspace->node);
638 	seat_set_focus(seat, focus);
639 	workspace_output_raise_priority(workspace, old_output, output);
640 	ipc_event_workspace(NULL, workspace, "move");
641 }
642 
cmd_move_workspace(int argc,char ** argv)643 static struct cmd_results *cmd_move_workspace(int argc, char **argv) {
644 	struct cmd_results *error = NULL;
645 	if ((error = checkarg(argc, "move workspace", EXPECTED_AT_LEAST, 1))) {
646 		return error;
647 	}
648 
649 	if (strcasecmp(argv[0], "output") == 0) {
650 		--argc; ++argv;
651 	}
652 
653 	if (!argc) {
654 		return cmd_results_new(CMD_INVALID,
655 				"Expected 'move workspace to [output] <output>'");
656 	}
657 
658 	struct sway_workspace *workspace = config->handler_context.workspace;
659 	if (!workspace) {
660 		return cmd_results_new(CMD_FAILURE, "No workspace to move");
661 	}
662 
663 	struct sway_output *old_output = workspace->output;
664 	int center_x = workspace->width / 2 + workspace->x,
665 		center_y = workspace->height / 2 + workspace->y;
666 	struct sway_output *new_output = output_in_direction(argv[0],
667 			old_output, center_x, center_y);
668 	if (!new_output) {
669 		return cmd_results_new(CMD_FAILURE,
670 			"Can't find output with name/direction '%s'", argv[0]);
671 	}
672 	workspace_move_to_output(workspace, new_output);
673 
674 	arrange_output(old_output);
675 	arrange_output(new_output);
676 
677 	return cmd_results_new(CMD_SUCCESS, NULL);
678 }
679 
cmd_move_in_direction(enum wlr_direction direction,int argc,char ** argv)680 static struct cmd_results *cmd_move_in_direction(
681 		enum wlr_direction direction, int argc, char **argv) {
682 	int move_amt = 10;
683 	if (argc) {
684 		char *inv;
685 		move_amt = (int)strtol(argv[0], &inv, 10);
686 		if (*inv != '\0' && strcasecmp(inv, "px") != 0) {
687 			return cmd_results_new(CMD_FAILURE, "Invalid distance specified");
688 		}
689 	}
690 
691 	struct sway_container *container = config->handler_context.container;
692 	if (!container) {
693 		return cmd_results_new(CMD_FAILURE,
694 				"Cannot move workspaces in a direction");
695 	}
696 	if (container_is_floating(container)) {
697 		if (container->fullscreen_mode) {
698 			return cmd_results_new(CMD_FAILURE,
699 					"Cannot move fullscreen floating container");
700 		}
701 		double lx = container->x;
702 		double ly = container->y;
703 		switch (direction) {
704 		case WLR_DIRECTION_LEFT:
705 			lx -= move_amt;
706 			break;
707 		case WLR_DIRECTION_RIGHT:
708 			lx += move_amt;
709 			break;
710 		case WLR_DIRECTION_UP:
711 			ly -= move_amt;
712 			break;
713 		case WLR_DIRECTION_DOWN:
714 			ly += move_amt;
715 			break;
716 		}
717 		container_floating_move_to(container, lx, ly);
718 		return cmd_results_new(CMD_SUCCESS, NULL);
719 	}
720 	struct sway_workspace *old_ws = container->workspace;
721 
722 	if (!container_move_in_direction(container, direction)) {
723 		// Container didn't move
724 		return cmd_results_new(CMD_SUCCESS, NULL);
725 	}
726 
727 	struct sway_workspace *new_ws = container->workspace;
728 
729 	if (root->fullscreen_global) {
730 		arrange_root();
731 	} else {
732 		arrange_workspace(old_ws);
733 		if (new_ws != old_ws) {
734 			arrange_workspace(new_ws);
735 		}
736 	}
737 
738 	if (container->view) {
739 		ipc_event_window(container, "move");
740 	}
741 
742 	// Hack to re-focus container
743 	seat_set_raw_focus(config->handler_context.seat, &new_ws->node);
744 	seat_set_focus_container(config->handler_context.seat, container);
745 
746 	if (old_ws != new_ws) {
747 		ipc_event_workspace(old_ws, new_ws, "focus");
748 		workspace_detect_urgent(old_ws);
749 		workspace_detect_urgent(new_ws);
750 	}
751 	container_end_mouse_operation(container);
752 
753 	return cmd_results_new(CMD_SUCCESS, NULL);
754 }
755 
756 static const char expected_position_syntax[] =
757 	"Expected 'move [absolute] position <x> [px] <y> [px]' or "
758 	"'move [absolute] position center' or "
759 	"'move position cursor|mouse|pointer'";
760 
cmd_move_to_position(int argc,char ** argv)761 static struct cmd_results *cmd_move_to_position(int argc, char **argv) {
762 	struct sway_container *container = config->handler_context.container;
763 	if (!container || !container_is_floating(container)) {
764 		return cmd_results_new(CMD_FAILURE, "Only floating containers "
765 				"can be moved to an absolute position");
766 	}
767 
768 	if (!argc) {
769 		return cmd_results_new(CMD_INVALID, expected_position_syntax);
770 	}
771 
772 	bool absolute = false;
773 	if (strcmp(argv[0], "absolute") == 0) {
774 		absolute = true;
775 		--argc;
776 		++argv;
777 	}
778 	if (!argc) {
779 		return cmd_results_new(CMD_INVALID, expected_position_syntax);
780 	}
781 	if (strcmp(argv[0], "position") == 0) {
782 		--argc;
783 		++argv;
784 	}
785 	if (!argc) {
786 		return cmd_results_new(CMD_INVALID, expected_position_syntax);
787 	}
788 	if (strcmp(argv[0], "cursor") == 0 || strcmp(argv[0], "mouse") == 0 ||
789 			strcmp(argv[0], "pointer") == 0) {
790 		if (absolute) {
791 			return cmd_results_new(CMD_INVALID, expected_position_syntax);
792 		}
793 		struct sway_seat *seat = config->handler_context.seat;
794 		if (!seat->cursor) {
795 			return cmd_results_new(CMD_FAILURE, "No cursor device");
796 		}
797 		double lx = seat->cursor->cursor->x - container->width / 2;
798 		double ly = seat->cursor->cursor->y - container->height / 2;
799 		container_floating_move_to(container, lx, ly);
800 		return cmd_results_new(CMD_SUCCESS, NULL);
801 	} else if (strcmp(argv[0], "center") == 0) {
802 		double lx, ly;
803 		if (absolute) {
804 			lx = root->x + (root->width - container->width) / 2;
805 			ly = root->y + (root->height - container->height) / 2;
806 		} else {
807 			struct sway_workspace *ws = container->workspace;
808 			if (!ws) {
809 				struct sway_seat *seat = config->handler_context.seat;
810 				ws = seat_get_focused_workspace(seat);
811 			}
812 			lx = ws->x + (ws->width - container->width) / 2;
813 			ly = ws->y + (ws->height - container->height) / 2;
814 		}
815 		container_floating_move_to(container, lx, ly);
816 		return cmd_results_new(CMD_SUCCESS, NULL);
817 	}
818 
819 	if (argc < 2) {
820 		return cmd_results_new(CMD_FAILURE, expected_position_syntax);
821 	}
822 
823 	double lx, ly;
824 	char *inv;
825 	lx = (double)strtol(argv[0], &inv, 10);
826 	if (*inv != '\0' && strcasecmp(inv, "px") != 0) {
827 		return cmd_results_new(CMD_FAILURE, "Invalid position specified");
828 	}
829 	if (strcmp(argv[1], "px") == 0) {
830 		--argc;
831 		++argv;
832 	}
833 
834 	if (argc > 3) {
835 		return cmd_results_new(CMD_FAILURE, expected_position_syntax);
836 	}
837 
838 	ly = (double)strtol(argv[1], &inv, 10);
839 	if ((*inv != '\0' && strcasecmp(inv, "px") != 0) ||
840 			(argc == 3 && strcmp(argv[2], "px") != 0)) {
841 		return cmd_results_new(CMD_FAILURE, "Invalid position specified");
842 	}
843 
844 	if (!absolute) {
845 		struct sway_workspace *ws = container->workspace;
846 		if (!ws) {
847 			struct sway_seat *seat = config->handler_context.seat;
848 			ws = seat_get_focused_workspace(seat);
849 		}
850 		lx += ws->x;
851 		ly += ws->y;
852 	}
853 	container_floating_move_to(container, lx, ly);
854 	return cmd_results_new(CMD_SUCCESS, NULL);
855 }
856 
cmd_move_to_scratchpad(void)857 static struct cmd_results *cmd_move_to_scratchpad(void) {
858 	struct sway_node *node = config->handler_context.node;
859 	struct sway_container *con = config->handler_context.container;
860 	struct sway_workspace *ws = config->handler_context.workspace;
861 	if (node->type == N_WORKSPACE && ws->tiling->length == 0) {
862 		return cmd_results_new(CMD_INVALID,
863 				"Can't move an empty workspace to the scratchpad");
864 	}
865 	if (node->type == N_WORKSPACE) {
866 		// Wrap the workspace's children in a container
867 		con = workspace_wrap_children(ws);
868 		ws->layout = L_HORIZ;
869 	}
870 
871 	// If the container is in a floating split container,
872 	// operate on the split container instead of the child.
873 	if (container_is_floating_or_child(con)) {
874 		while (con->parent) {
875 			con = con->parent;
876 		}
877 	}
878 
879 	if (!con->scratchpad) {
880 		root_scratchpad_add_container(con, NULL);
881 	} else if (con->workspace) {
882 		root_scratchpad_hide(con);
883 	}
884 	return cmd_results_new(CMD_SUCCESS, NULL);
885 }
886 
887 static const char expected_full_syntax[] = "Expected "
888 	"'move left|right|up|down [<amount> [px]]'"
889 	" or 'move [--no-auto-back-and-forth] [window|container] [to] workspace"
890 	"  <name>|next|prev|next_on_output|prev_on_output|current|(number <num>)'"
891 	" or 'move [window|container] [to] output <name/id>|left|right|up|down'"
892 	" or 'move [window|container] [to] mark <mark>'"
893 	" or 'move [window|container] [to] scratchpad'"
894 	" or 'move workspace to [output] <name/id>|left|right|up|down'"
895 	" or 'move [window|container] [to] [absolute] position <x> [px] <y> [px]'"
896 	" or 'move [window|container] [to] [absolute] position center'"
897 	" or 'move [window|container] [to] position mouse|cursor|pointer'";
898 
cmd_move(int argc,char ** argv)899 struct cmd_results *cmd_move(int argc, char **argv) {
900 	struct cmd_results *error = NULL;
901 	if ((error = checkarg(argc, "move", EXPECTED_AT_LEAST, 1))) {
902 		return error;
903 	}
904 	if (!root->outputs->length) {
905 		return cmd_results_new(CMD_INVALID,
906 				"Can't run this command while there's no outputs connected.");
907 	}
908 
909 	if (strcasecmp(argv[0], "left") == 0) {
910 		return cmd_move_in_direction(WLR_DIRECTION_LEFT, --argc, ++argv);
911 	} else if (strcasecmp(argv[0], "right") == 0) {
912 		return cmd_move_in_direction(WLR_DIRECTION_RIGHT, --argc, ++argv);
913 	} else if (strcasecmp(argv[0], "up") == 0) {
914 		return cmd_move_in_direction(WLR_DIRECTION_UP, --argc, ++argv);
915 	} else if (strcasecmp(argv[0], "down") == 0) {
916 		return cmd_move_in_direction(WLR_DIRECTION_DOWN, --argc, ++argv);
917 	} else if (strcasecmp(argv[0], "workspace") == 0 && argc >= 2
918 			&& (strcasecmp(argv[1], "to") == 0 ||
919 				strcasecmp(argv[1], "output") == 0)) {
920 		argc -= 2; argv += 2;
921 		return cmd_move_workspace(argc, argv);
922 	}
923 
924 	bool no_auto_back_and_forth = false;
925 	if (strcasecmp(argv[0], "--no-auto-back-and-forth") == 0) {
926 		no_auto_back_and_forth = true;
927 		--argc; ++argv;
928 	}
929 
930 	if (argc > 0 && (strcasecmp(argv[0], "window") == 0 ||
931 			strcasecmp(argv[0], "container") == 0)) {
932 		--argc; ++argv;
933 	}
934 
935 	if (argc > 0 && strcasecmp(argv[0], "to") == 0) {
936 		--argc;	++argv;
937 	}
938 
939 	if (!argc) {
940 		return cmd_results_new(CMD_INVALID, expected_full_syntax);
941 	}
942 
943 	// Only `move [window|container] [to] workspace` supports
944 	// `--no-auto-back-and-forth` so treat others as invalid syntax
945 	if (no_auto_back_and_forth && strcasecmp(argv[0], "workspace") != 0) {
946 		return cmd_results_new(CMD_INVALID, expected_full_syntax);
947 	}
948 
949 	if (strcasecmp(argv[0], "workspace") == 0 ||
950 			strcasecmp(argv[0], "output") == 0 ||
951 			strcasecmp(argv[0], "mark") == 0) {
952 		return cmd_move_container(no_auto_back_and_forth, argc, argv);
953 	} else if (strcasecmp(argv[0], "scratchpad") == 0) {
954 		return cmd_move_to_scratchpad();
955 	} else if (strcasecmp(argv[0], "position") == 0 ||
956 			(argc > 1 && strcasecmp(argv[0], "absolute") == 0 &&
957 			strcasecmp(argv[1], "position") == 0)) {
958 		return cmd_move_to_position(argc, argv);
959 	}
960 	return cmd_results_new(CMD_INVALID, expected_full_syntax);
961 }
962