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