aboutsummaryrefslogtreecommitdiff
path: root/src/move.c
blob: d368a4d33a72ac9287c225b3abeb8b8a02af64b2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
/*
 * vim:ts=4:sw=4:expandtab
 *
 * i3 - an improved dynamic tiling window manager
 * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
 *
 * move.c: Moving containers into some direction.
 *
 */
#include "all.h"

/*
 * Returns the lowest container in the tree that has both a and b as descendants.
 *
 */
static Con *lowest_common_ancestor(Con *a, Con *b) {
    Con *parent_a = a;
    while (parent_a) {
        Con *parent_b = b;
        while (parent_b) {
            if (parent_a == parent_b) {
                return parent_a;
            }
            parent_b = parent_b->parent;
        }
        parent_a = parent_a->parent;
    }
    assert(false);
}

/*
 * Returns the direct child of ancestor that contains con.
 *
 */
static Con *child_containing_con_recursively(Con *ancestor, Con *con) {
    Con *child = con;
    while (child && child->parent != ancestor) {
        child = child->parent;
        assert(child->parent);
    }
    return child;
}

/*
 * Returns true if the given container is the focused descendant of ancestor, recursively.
 *
 */
static bool is_focused_descendant(Con *con, Con *ancestor) {
    Con *current = con;
    while (current != ancestor) {
        if (TAILQ_FIRST(&(current->parent->focus_head)) != current) {
            return false;
        }
        current = current->parent;
        assert(current->parent);
    }
    return true;
}

/*
 * This function detaches 'con' from its parent and inserts it either before or
 * after 'target'.
 *
 */
void insert_con_into(Con *con, Con *target, position_t position) {
    Con *parent = target->parent;
    /* We need to preserve the old con->parent. While it might still be used to
     * insert the entry before/after it, we call the on_remove_child callback
     * afterwards which might then close the con if it is empty. */
    Con *old_parent = con->parent;

    /* We compare the focus order of the children of the lowest common ancestor. If con or
     * its ancestor is before target's ancestor then con should be placed before the target
     * in the focus stack. */
    Con *lca = lowest_common_ancestor(con, parent);
    if (lca == con) {
        ELOG("Container is being inserted into one of its descendants.\n");
        return;
    }

    Con *con_ancestor = child_containing_con_recursively(lca, con);
    Con *target_ancestor = child_containing_con_recursively(lca, target);
    bool moves_focus_from_ancestor = is_focused_descendant(con, con_ancestor);
    bool focus_before;

    /* Determine if con is going to be placed before or after target in the parent's focus stack. */
    if (con_ancestor == target_ancestor) {
        /* Happens when the target is con's old parent. Eg with layout V [ A H [ B C ] ],
         * if we move C up. Target will be H. */
        focus_before = moves_focus_from_ancestor;
    } else {
        /* Look at the focus stack order of the children of the lowest common ancestor. */
        Con *current;
        TAILQ_FOREACH (current, &(lca->focus_head), focused) {
            if (current == con_ancestor || current == target_ancestor) {
                break;
            }
        }
        focus_before = (current == con_ancestor);
    }

    /* If con is the focused container in our old ancestor we place the new ancestor
     * before the old ancestor in the focus stack. Example:
     * Consider the layout [ H [ V1 [ A* B ] V2 [ C ] ] ] where A is focused. We move to
     * a second workspace and from there we move A to the right and switch back to the
     * original workspace. Without the change focus would move to B instead of staying
     * with A. */
    if (moves_focus_from_ancestor && focus_before) {
        Con *place = TAILQ_PREV(con_ancestor, focus_head, focused);
        TAILQ_REMOVE(&(lca->focus_head), target_ancestor, focused);
        if (place) {
            TAILQ_INSERT_AFTER(&(lca->focus_head), place, target_ancestor, focused);
        } else {
            TAILQ_INSERT_HEAD(&(lca->focus_head), target_ancestor, focused);
        }
    }

    con_detach(con);
    con_fix_percent(con->parent);

    /* When moving to a workspace, we respect the user’s configured
     * workspace_layout */
    if (parent->type == CT_WORKSPACE) {
        Con *split = workspace_attach_to(parent);
        if (split != parent) {
            DLOG("Got a new split con, using that one instead\n");
            con->parent = split;
            con_attach(con, split, false);
            DLOG("attached\n");
            con->percent = 0.0;
            con_fix_percent(split);
            con = split;
            DLOG("ok, continuing with con %p instead\n", con);
            con_detach(con);
        }
    }

    con->parent = parent;

    if (parent == lca) {
        if (focus_before) {
            /* Example layout: H [ A B* ], we move A up/down. 'target' will be H. */
            TAILQ_INSERT_BEFORE(target, con, focused);
        } else {
            /* Example layout: H [ A B* ], we move A up/down. 'target' will be H. */
            TAILQ_INSERT_AFTER(&(parent->focus_head), target, con, focused);
        }
    } else {
        if (focus_before) {
            /* Example layout: V [ H [ A B ] C* ], we move C up. 'target' will be A. */
            TAILQ_INSERT_HEAD(&(parent->focus_head), con, focused);
        } else {
            /* Example layout: V [ H [ A* B ] C ], we move C up. 'target' will be A. */
            TAILQ_INSERT_TAIL(&(parent->focus_head), con, focused);
        }
    }

    if (position == BEFORE) {
        TAILQ_INSERT_BEFORE(target, con, nodes);
    } else if (position == AFTER) {
        TAILQ_INSERT_AFTER(&(parent->nodes_head), target, con, nodes);
    }

    /* Pretend the con was just opened with regards to size percent values.
     * Since the con is moved to a completely different con, the old value
     * does not make sense anyways. */
    con->percent = 0.0;
    con_fix_percent(parent);

    CALL(old_parent, on_remove_child);
}

/*
 * This function detaches 'con' from its parent and puts it in the given
 * workspace. Position is determined by the direction of movement into the
 * workspace container.
 *
 */
static void attach_to_workspace(Con *con, Con *ws, direction_t direction) {
    con_detach(con);
    Con *old_parent = con->parent;
    con->parent = ws;

    if (direction == D_RIGHT || direction == D_DOWN) {
        TAILQ_INSERT_HEAD(&(ws->nodes_head), con, nodes);
    } else {
        TAILQ_INSERT_TAIL(&(ws->nodes_head), con, nodes);
    }
    TAILQ_INSERT_TAIL(&(ws->focus_head), con, focused);

    /* Pretend the con was just opened with regards to size percent values.
     * Since the con is moved to a completely different con, the old value
     * does not make sense anyways. */
    con->percent = 0.0;
    con_fix_percent(ws);

    con_fix_percent(old_parent);
    CALL(old_parent, on_remove_child);
}

/*
 * Moves the given container to the closest output in the given direction if
 * such an output exists.
 *
 */
static void move_to_output_directed(Con *con, direction_t direction) {
    Output *current_output = get_output_for_con(con);
    Output *output = get_output_next(direction, current_output, CLOSEST_OUTPUT);

    if (!output) {
        DLOG("No output in this direction found. Not moving.\n");
        return;
    }

    Con *ws = NULL;
    GREP_FIRST(ws, output_get_content(output->con), workspace_is_visible(child));

    if (!ws) {
        DLOG("No workspace on output in this direction found. Not moving.\n");
        return;
    }

    Con *old_ws = con_get_workspace(con);
    const bool moves_focus = (focused == con);
    attach_to_workspace(con, ws, direction);
    if (moves_focus) {
        /* workspace_show will not correctly update the active workspace because
         * the focused container, con, is now a child of ws. To work around this
         * and still produce the correct workspace focus events (see
         * 517-regress-move-direction-ipc.t) we need to temporarily set focused
         * to the old workspace.
         *
         * The following happen:
         * 1. Focus con to push it on the top of the focus stack in its new
         * workspace
         * 2. Set focused to the old workspace to force workspace_show to
         * execute
         * 3. workspace_show will descend focus and target our con for
         * focusing. This also ensures that the mouse warps correctly.
         * See: #3518. */
        con_focus(con);
        focused = old_ws;
        workspace_show(ws);
        con_focus(con);
    }

    /* force re-painting the indicators */
    FREE(con->deco_render_params);

    ipc_send_window_event("move", con);
    tree_flatten(croot);
    ewmh_update_wm_desktop();
}

/*
 * Moves the given container in the given direction
 *
 */
void tree_move(Con *con, direction_t direction) {
    position_t position;
    Con *target;

    DLOG("Moving in direction %d\n", direction);

    /* 1: get the first parent with the same orientation */

    if (con->type == CT_WORKSPACE) {
        DLOG("Not moving workspace\n");
        return;
    }

    if (con->fullscreen_mode == CF_GLOBAL) {
        DLOG("Not moving fullscreen global container\n");
        return;
    }

    if ((con->fullscreen_mode == CF_OUTPUT) ||
        (con->parent->type == CT_WORKSPACE && con_num_children(con->parent) == 1)) {
        /* This is the only con on this workspace */
        move_to_output_directed(con, direction);
        return;
    }

    orientation_t o = orientation_from_direction(direction);

    Con *same_orientation = con_parent_with_orientation(con, o);
    /* The do {} while is used to 'restart' at this point with a different
     * same_orientation, see the very last lines before the end of this block
     * */
    do {
        /* There is no parent container with the same orientation */
        if (!same_orientation) {
            if (con_is_floating(con)) {
                /* this is a floating con, we just disable floating */
                floating_disable(con);
                return;
            }
            if (con_inside_floating(con)) {
                /* 'con' should be moved out of a floating container */
                DLOG("Inside floating, moving to workspace\n");
                attach_to_workspace(con, con_get_workspace(con), direction);
                goto end;
            }
            DLOG("Force-changing orientation\n");
            ws_force_orientation(con_get_workspace(con), o);
            same_orientation = con_parent_with_orientation(con, o);
        }

        /* easy case: the move is within this container */
        if (same_orientation == con->parent) {
            Con *swap = (direction == D_LEFT || direction == D_UP)
                            ? TAILQ_PREV(con, nodes_head, nodes)
                            : TAILQ_NEXT(con, nodes);
            if (swap) {
                if (!con_is_leaf(swap)) {
                    DLOG("Moving into our bordering branch\n");
                    target = con_descend_direction(swap, direction);
                    position = (con_orientation(target->parent) != o ||
                                        direction == D_UP ||
                                        direction == D_LEFT
                                    ? AFTER
                                    : BEFORE);
                    insert_con_into(con, target, position);
                    goto end;
                }

                DLOG("Swapping with sibling.\n");
                if (direction == D_LEFT || direction == D_UP) {
                    TAILQ_SWAP(swap, con, &(swap->parent->nodes_head), nodes);
                } else {
                    TAILQ_SWAP(con, swap, &(swap->parent->nodes_head), nodes);
                }

                /* redraw parents to ensure all parent split container titles are updated correctly */
                con_force_split_parents_redraw(con);

                ipc_send_window_event("move", con);
                return;
            }

            if (con->parent == con_get_workspace(con)) {
                /* If we couldn't find a place to move it on this workspace, try
                 * to move it to a workspace on a different output */
                move_to_output_directed(con, direction);
                return;
            }

            /* If there was no con with which we could swap the current one,
             * search again, but starting one level higher. */
            same_orientation = con_parent_with_orientation(con->parent, o);
        }
    } while (same_orientation == NULL);

    /* this time, we have to move to another container */
    /* This is the container *above* 'con' (an ancestor of con) which is inside
     * 'same_orientation' */
    Con *above = con;
    while (above->parent != same_orientation)
        above = above->parent;

    /* Enforce the fullscreen focus restrictions. */
    if (!con_fullscreen_permits_focusing(above->parent)) {
        LOG("Cannot move out of fullscreen container\n");
        return;
    }

    DLOG("above = %p\n", above);

    Con *next = (direction == D_UP || direction == D_LEFT ? TAILQ_PREV(above, nodes_head, nodes) : TAILQ_NEXT(above, nodes));

    if (next && !con_is_leaf(next)) {
        DLOG("Moving into the bordering branch of our adjacent container\n");
        target = con_descend_direction(next, direction);
        position = (con_orientation(target->parent) != o ||
                            direction == D_UP ||
                            direction == D_LEFT
                        ? AFTER
                        : BEFORE);
        insert_con_into(con, target, position);
    } else if (!next &&
               con->parent->parent->type == CT_WORKSPACE &&
               con->parent->layout != L_DEFAULT &&
               con_num_children(con->parent) == 1) {
        /* Con is the lone child of a non-default layout container at the edge
         * of the workspace. Treat it as though the workspace is its parent
         * and move it to the next output. */
        DLOG("Grandparent is workspace\n");
        move_to_output_directed(con, direction);
        return;
    } else {
        DLOG("Moving into container above\n");
        position = (direction == D_UP || direction == D_LEFT ? BEFORE : AFTER);
        insert_con_into(con, above, position);
    }

end:
    /* force re-painting the indicators */
    FREE(con->deco_render_params);

    ipc_send_window_event("move", con);
    tree_flatten(croot);
    ewmh_update_wm_desktop();
}