i3
move.c
Go to the documentation of this file.
1 /*
2  * vim:ts=4:sw=4:expandtab
3  *
4  * i3 - an improved dynamic tiling window manager
5  * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
6  *
7  * move.c: Moving containers into some direction.
8  *
9  */
10 #include "all.h"
11 
12 /*
13  * Returns the lowest container in the tree that has both a and b as descendants.
14  *
15  */
16 static Con *lowest_common_ancestor(Con *a, Con *b) {
17  Con *parent_a = a;
18  while (parent_a) {
19  Con *parent_b = b;
20  while (parent_b) {
21  if (parent_a == parent_b) {
22  return parent_a;
23  }
24  parent_b = parent_b->parent;
25  }
26  parent_a = parent_a->parent;
27  }
28  assert(false);
29 }
30 
31 /*
32  * Returns the direct child of ancestor that contains con.
33  *
34  */
35 static Con *child_containing_con_recursively(Con *ancestor, Con *con) {
36  Con *child = con;
37  while (child && child->parent != ancestor) {
38  child = child->parent;
39  assert(child->parent);
40  }
41  return child;
42 }
43 
44 /*
45  * Returns true if the given container is the focused descendant of ancestor, recursively.
46  *
47  */
48 static bool is_focused_descendant(Con *con, Con *ancestor) {
49  Con *current = con;
50  while (current != ancestor) {
51  if (TAILQ_FIRST(&(current->parent->focus_head)) != current) {
52  return false;
53  }
54  current = current->parent;
55  assert(current->parent);
56  }
57  return true;
58 }
59 
60 /*
61  * This function detaches 'con' from its parent and inserts it either before or
62  * after 'target'.
63  *
64  */
65 void insert_con_into(Con *con, Con *target, position_t position) {
66  Con *parent = target->parent;
67  /* We need to preserve the old con->parent. While it might still be used to
68  * insert the entry before/after it, we call the on_remove_child callback
69  * afterwards which might then close the con if it is empty. */
70  Con *old_parent = con->parent;
71 
72  /* We compare the focus order of the children of the lowest common ancestor. If con or
73  * its ancestor is before target's ancestor then con should be placed before the target
74  * in the focus stack. */
75  Con *lca = lowest_common_ancestor(con, parent);
76  if (lca == con) {
77  ELOG("Container is being inserted into one of its descendants.\n");
78  return;
79  }
80 
81  Con *con_ancestor = child_containing_con_recursively(lca, con);
82  Con *target_ancestor = child_containing_con_recursively(lca, target);
83  bool moves_focus_from_ancestor = is_focused_descendant(con, con_ancestor);
84  bool focus_before;
85 
86  /* Determine if con is going to be placed before or after target in the parent's focus stack. */
87  if (con_ancestor == target_ancestor) {
88  /* Happens when the target is con's old parent. Eg with layout V [ A H [ B C ] ],
89  * if we move C up. Target will be H. */
90  focus_before = moves_focus_from_ancestor;
91  } else {
92  /* Look at the focus stack order of the children of the lowest common ancestor. */
93  Con *current;
94  TAILQ_FOREACH (current, &(lca->focus_head), focused) {
95  if (current == con_ancestor || current == target_ancestor) {
96  break;
97  }
98  }
99  focus_before = (current == con_ancestor);
100  }
101 
102  /* If con is the focused container in our old ancestor we place the new ancestor
103  * before the old ancestor in the focus stack. Example:
104  * Consider the layout [ H [ V1 [ A* B ] V2 [ C ] ] ] where A is focused. We move to
105  * a second workspace and from there we move A to the right and switch back to the
106  * original workspace. Without the change focus would move to B instead of staying
107  * with A. */
108  if (moves_focus_from_ancestor && focus_before) {
109  Con *place = TAILQ_PREV(con_ancestor, focus_head, focused);
110  TAILQ_REMOVE(&(lca->focus_head), target_ancestor, focused);
111  if (place) {
112  TAILQ_INSERT_AFTER(&(lca->focus_head), place, target_ancestor, focused);
113  } else {
114  TAILQ_INSERT_HEAD(&(lca->focus_head), target_ancestor, focused);
115  }
116  }
117 
118  con_detach(con);
119  con_fix_percent(con->parent);
120 
121  /* When moving to a workspace, we respect the user’s configured
122  * workspace_layout */
123  if (parent->type == CT_WORKSPACE) {
124  Con *split = workspace_attach_to(parent);
125  if (split != parent) {
126  DLOG("Got a new split con, using that one instead\n");
127  con->parent = split;
128  con_attach(con, split, false);
129  DLOG("attached\n");
130  con->percent = 0.0;
131  con_fix_percent(split);
132  con = split;
133  DLOG("ok, continuing with con %p instead\n", con);
134  con_detach(con);
135  }
136  }
137 
138  con->parent = parent;
139 
140  if (parent == lca) {
141  if (focus_before) {
142  /* Example layout: H [ A B* ], we move A up/down. 'target' will be H. */
143  TAILQ_INSERT_BEFORE(target, con, focused);
144  } else {
145  /* Example layout: H [ A B* ], we move A up/down. 'target' will be H. */
146  TAILQ_INSERT_AFTER(&(parent->focus_head), target, con, focused);
147  }
148  } else {
149  if (focus_before) {
150  /* Example layout: V [ H [ A B ] C* ], we move C up. 'target' will be A. */
151  TAILQ_INSERT_HEAD(&(parent->focus_head), con, focused);
152  } else {
153  /* Example layout: V [ H [ A* B ] C ], we move C up. 'target' will be A. */
154  TAILQ_INSERT_TAIL(&(parent->focus_head), con, focused);
155  }
156  }
157 
158  if (position == BEFORE) {
159  TAILQ_INSERT_BEFORE(target, con, nodes);
160  } else if (position == AFTER) {
161  TAILQ_INSERT_AFTER(&(parent->nodes_head), target, con, nodes);
162  }
163 
164  /* Pretend the con was just opened with regards to size percent values.
165  * Since the con is moved to a completely different con, the old value
166  * does not make sense anyways. */
167  con->percent = 0.0;
168  con_fix_percent(parent);
169 
170  CALL(old_parent, on_remove_child);
171 }
172 
173 /*
174  * This function detaches 'con' from its parent and puts it in the given
175  * workspace. Position is determined by the direction of movement into the
176  * workspace container.
177  *
178  */
179 static void attach_to_workspace(Con *con, Con *ws, direction_t direction) {
180  con_detach(con);
181  Con *old_parent = con->parent;
182  con->parent = ws;
183 
184  if (direction == D_RIGHT || direction == D_DOWN) {
185  TAILQ_INSERT_HEAD(&(ws->nodes_head), con, nodes);
186  } else {
187  TAILQ_INSERT_TAIL(&(ws->nodes_head), con, nodes);
188  }
189  TAILQ_INSERT_TAIL(&(ws->focus_head), con, focused);
190 
191  /* Pretend the con was just opened with regards to size percent values.
192  * Since the con is moved to a completely different con, the old value
193  * does not make sense anyways. */
194  con->percent = 0.0;
195  con_fix_percent(ws);
196 
197  con_fix_percent(old_parent);
198  CALL(old_parent, on_remove_child);
199 }
200 
201 /*
202  * Moves the given container to the closest output in the given direction if
203  * such an output exists.
204  *
205  */
206 static void move_to_output_directed(Con *con, direction_t direction) {
207  Output *current_output = get_output_for_con(con);
208  Output *output = get_output_next(direction, current_output, CLOSEST_OUTPUT);
209 
210  if (!output) {
211  DLOG("No output in this direction found. Not moving.\n");
212  return;
213  }
214 
215  Con *ws = NULL;
217 
218  if (!ws) {
219  DLOG("No workspace on output in this direction found. Not moving.\n");
220  return;
221  }
222 
223  Con *old_ws = con_get_workspace(con);
224  const bool moves_focus = (focused == con);
225  attach_to_workspace(con, ws, direction);
226  if (moves_focus) {
227  /* workspace_show will not correctly update the active workspace because
228  * the focused container, con, is now a child of ws. To work around this
229  * and still produce the correct workspace focus events (see
230  * 517-regress-move-direction-ipc.t) we need to temporarily set focused
231  * to the old workspace. */
232  focused = old_ws;
233  workspace_show(ws);
234  con_focus(con);
235  }
236 
237  /* force re-painting the indicators */
238  FREE(con->deco_render_params);
239 
240  ipc_send_window_event("move", con);
243 }
244 
245 /*
246  * Moves the given container in the given direction
247  *
248  */
249 void tree_move(Con *con, direction_t direction) {
250  position_t position;
251  Con *target;
252 
253  DLOG("Moving in direction %d\n", direction);
254 
255  /* 1: get the first parent with the same orientation */
256 
257  if (con->type == CT_WORKSPACE) {
258  DLOG("Not moving workspace\n");
259  return;
260  }
261 
262  if (con->fullscreen_mode == CF_GLOBAL) {
263  DLOG("Not moving fullscreen global container\n");
264  return;
265  }
266 
267  if ((con->fullscreen_mode == CF_OUTPUT) ||
268  (con->parent->type == CT_WORKSPACE && con_num_children(con->parent) == 1)) {
269  /* This is the only con on this workspace */
270  move_to_output_directed(con, direction);
271  return;
272  }
273 
275 
276  Con *same_orientation = con_parent_with_orientation(con, o);
277  /* The do {} while is used to 'restart' at this point with a different
278  * same_orientation, see the very last lines before the end of this block
279  * */
280  do {
281  /* There is no parent container with the same orientation */
282  if (!same_orientation) {
283  if (con_is_floating(con)) {
284  /* this is a floating con, we just disable floating */
285  floating_disable(con);
286  return;
287  }
288  if (con_inside_floating(con)) {
289  /* 'con' should be moved out of a floating container */
290  DLOG("Inside floating, moving to workspace\n");
291  attach_to_workspace(con, con_get_workspace(con), direction);
292  goto end;
293  }
294  DLOG("Force-changing orientation\n");
296  same_orientation = con_parent_with_orientation(con, o);
297  }
298 
299  /* easy case: the move is within this container */
300  if (same_orientation == con->parent) {
301  Con *swap = (direction == D_LEFT || direction == D_UP)
302  ? TAILQ_PREV(con, nodes_head, nodes)
303  : TAILQ_NEXT(con, nodes);
304  if (swap) {
305  if (!con_is_leaf(swap)) {
306  DLOG("Moving into our bordering branch\n");
307  target = con_descend_direction(swap, direction);
308  position = (con_orientation(target->parent) != o ||
309  direction == D_UP ||
310  direction == D_LEFT
311  ? AFTER
312  : BEFORE);
313  insert_con_into(con, target, position);
314  goto end;
315  }
316 
317  DLOG("Swapping with sibling.\n");
318  if (direction == D_LEFT || direction == D_UP) {
319  TAILQ_SWAP(swap, con, &(swap->parent->nodes_head), nodes);
320  } else {
321  TAILQ_SWAP(con, swap, &(swap->parent->nodes_head), nodes);
322  }
323 
324  ipc_send_window_event("move", con);
325  return;
326  }
327 
328  if (con->parent == con_get_workspace(con)) {
329  /* If we couldn't find a place to move it on this workspace, try
330  * to move it to a workspace on a different output */
331  move_to_output_directed(con, direction);
332  return;
333  }
334 
335  /* If there was no con with which we could swap the current one,
336  * search again, but starting one level higher. */
337  same_orientation = con_parent_with_orientation(con->parent, o);
338  }
339  } while (same_orientation == NULL);
340 
341  /* this time, we have to move to another container */
342  /* This is the container *above* 'con' (an ancestor of con) which is inside
343  * 'same_orientation' */
344  Con *above = con;
345  while (above->parent != same_orientation)
346  above = above->parent;
347 
348  /* Enforce the fullscreen focus restrictions. */
350  LOG("Cannot move out of fullscreen container\n");
351  return;
352  }
353 
354  DLOG("above = %p\n", above);
355 
356  Con *next = (direction == D_UP || direction == D_LEFT ? TAILQ_PREV(above, nodes_head, nodes) : TAILQ_NEXT(above, nodes));
357 
358  if (next && !con_is_leaf(next)) {
359  DLOG("Moving into the bordering branch of our adjacent container\n");
360  target = con_descend_direction(next, direction);
361  position = (con_orientation(target->parent) != o ||
362  direction == D_UP ||
363  direction == D_LEFT
364  ? AFTER
365  : BEFORE);
366  insert_con_into(con, target, position);
367  } else if (!next &&
368  con->parent->parent->type == CT_WORKSPACE &&
369  con->parent->layout != L_DEFAULT &&
370  con_num_children(con->parent) == 1) {
371  /* Con is the lone child of a non-default layout container at the edge
372  * of the workspace. Treat it as though the workspace is its parent
373  * and move it to the next output. */
374  DLOG("Grandparent is workspace\n");
375  move_to_output_directed(con, direction);
376  return;
377  } else {
378  DLOG("Moving into container above\n");
379  position = (direction == D_UP || direction == D_LEFT ? BEFORE : AFTER);
380  insert_con_into(con, above, position);
381  }
382 
383 end:
384  /* force re-painting the indicators */
385  FREE(con->deco_render_params);
386 
387  ipc_send_window_event("move", con);
390 }
Con * con_descend_direction(Con *con, direction_t direction)
Returns the leftmost, rightmost, etc.
Definition: con.c:1591
bool con_is_floating(Con *con)
Returns true if the node is floating.
Definition: con.c:596
orientation_t con_orientation(Con *con)
Returns the orientation of the given container (for stacked containers, vertical orientation is used ...
Definition: con.c:1477
Con * con_get_workspace(Con *con)
Gets the workspace container this node is on.
Definition: con.c:477
Con * con_parent_with_orientation(Con *con, orientation_t orientation)
Searches parents of the given 'con' until it reaches one with the specified 'orientation'.
Definition: con.c:489
bool con_fullscreen_permits_focusing(Con *con)
Returns true if changing the focus to con would be allowed considering the fullscreen focus constrain...
Definition: con.c:2103
void con_detach(Con *con)
Detaches the given container from its current parent.
Definition: con.c:230
void con_fix_percent(Con *con)
Updates the percent attribute of the children of the given container.
Definition: con.c:1011
Con * con_inside_floating(Con *con)
Checks if the given container is either floating or inside some floating container.
Definition: con.c:620
void con_attach(Con *con, Con *parent, bool ignore_focus)
Attaches the given container to the given parent.
Definition: con.c:222
bool con_is_leaf(Con *con)
Returns true when this node is a leaf node (has no children)
Definition: con.c:361
int con_num_children(Con *con)
Returns the number of children of this container.
Definition: con.c:947
void con_focus(Con *con)
Sets input focus to the given container.
Definition: con.c:246
void ewmh_update_wm_desktop(void)
Updates _NET_WM_DESKTOP for all windows.
Definition: ewmh.c:184
void floating_disable(Con *con)
Disables floating mode for the given container by re-attaching the container to its old parent.
Definition: floating.c:427
void ipc_send_window_event(const char *property, Con *con)
For the window events we send, along the usual "change" field, also the window container,...
Definition: ipc.c:1594
static Con * child_containing_con_recursively(Con *ancestor, Con *con)
Definition: move.c:35
static bool is_focused_descendant(Con *con, Con *ancestor)
Definition: move.c:48
static void attach_to_workspace(Con *con, Con *ws, direction_t direction)
Definition: move.c:179
void insert_con_into(Con *con, Con *target, position_t position)
This function detaches 'con' from its parent and inserts it either before or after 'target'.
Definition: move.c:65
static Con * lowest_common_ancestor(Con *a, Con *b)
Definition: move.c:16
void tree_move(Con *con, direction_t direction)
Moves the given container in the given direction.
Definition: move.c:249
static void move_to_output_directed(Con *con, direction_t direction)
Definition: move.c:206
Con * output_get_content(Con *output)
Returns the output container below the given output container.
Definition: output.c:16
Output * get_output_for_con(Con *con)
Returns the output for the given con.
Definition: output.c:57
Output * get_output_next(direction_t direction, Output *current, output_close_far_t close_far)
Gets the output which is the next one in the given direction.
Definition: randr.c:242
struct Con * focused
Definition: tree.c:13
void tree_flatten(Con *con)
tree_flatten() removes pairs of redundant split containers, e.g.
Definition: tree.c:651
struct Con * croot
Definition: tree.c:12
orientation_t orientation_from_direction(direction_t direction)
Convert a direction to its corresponding orientation.
Definition: util.c:456
void workspace_show(Con *workspace)
Switches to the given workspace.
Definition: workspace.c:420
bool workspace_is_visible(Con *ws)
Returns true if the workspace is currently visible.
Definition: workspace.c:306
Con * workspace_attach_to(Con *ws)
Called when a new con (with a window, not an empty or split con) should be attached to the workspace ...
Definition: workspace.c:902
void ws_force_orientation(Con *ws, orientation_t orientation)
'Forces' workspace orientation by moving all cons into a new split-con with the same orientation as t...
Definition: workspace.c:859
position_t
Definition: data.h:60
@ AFTER
Definition: data.h:61
@ BEFORE
Definition: data.h:60
@ L_DEFAULT
Definition: data.h:92
orientation_t
Definition: data.h:57
@ CF_OUTPUT
Definition: data.h:600
@ CF_GLOBAL
Definition: data.h:601
direction_t
Definition: data.h:53
@ D_RIGHT
Definition: data.h:54
@ D_LEFT
Definition: data.h:53
@ D_UP
Definition: data.h:55
@ D_DOWN
Definition: data.h:56
#define DLOG(fmt,...)
Definition: libi3.h:105
#define LOG(fmt,...)
Definition: libi3.h:95
#define ELOG(fmt,...)
Definition: libi3.h:100
#define TAILQ_FOREACH(var, head, field)
Definition: queue.h:347
#define TAILQ_SWAP(first, second, head, field)
Definition: queue.h:426
#define TAILQ_INSERT_TAIL(head, elm, field)
Definition: queue.h:376
#define TAILQ_PREV(elm, headname, field)
Definition: queue.h:342
#define TAILQ_FIRST(head)
Definition: queue.h:336
#define TAILQ_REMOVE(head, elm, field)
Definition: queue.h:402
#define TAILQ_NEXT(elm, field)
Definition: queue.h:338
#define TAILQ_INSERT_BEFORE(listelm, elm, field)
Definition: queue.h:394
#define TAILQ_INSERT_HEAD(head, elm, field)
Definition: queue.h:366
#define TAILQ_INSERT_AFTER(head, listelm, elm, field)
Definition: queue.h:384
@ CLOSEST_OUTPUT
Definition: randr.h:23
#define CALL(obj, member,...)
Definition: util.h:53
#define GREP_FIRST(dest, head, condition)
Definition: util.h:38
#define FREE(pointer)
Definition: util.h:47
An Output is a physical output on your graphics driver.
Definition: data.h:361
Con * con
Pointer to the Con which represents this output.
Definition: data.h:381
A 'Con' represents everything from the X11 root window down to a single X11 window.
Definition: data.h:613
struct Con * parent
Definition: data.h:645
enum Con::@18 type
double percent
Definition: data.h:679
layout_t layout
Definition: data.h:722
struct deco_render_params * deco_render_params
Cache for the decoration rendering.
Definition: data.h:691
fullscreen_mode_t fullscreen_mode
Definition: data.h:701