i3 - improved tiling WM


Command 'move <direction>' moves across outputs

Patch status: merged

Patch by Tony Crisci

Long description:

When 'move <direction>' is issued in the context of a container that
borders a workspace, and there is no suitable place within this
workspace for which this container can move, move the container to the
closest output in this direction instead.

To apply this patch, use:
curl http://cr.i3wm.org/patch/311/raw.patch | git am

b/src/move.c

19
@@ -65,11 +65,12 @@ static void insert_con_into(Con *con, Con *target, position_t position) {
20
 }
21
 
22
 /*
23
- * This function detaches 'con' from its parent and inserts it at the given
24
- * workspace.
25
+ * This function detaches 'con' from its parent and puts it in the given
26
+ * workspace. Position is determined by the direction of movement into the
27
+ * workspace container.
28
  *
29
  */
30
-static void attach_to_workspace(Con *con, Con *ws) {
31
+static void attach_to_workspace(Con *con, Con *ws, direction_t direction) {
32
     con_detach(con);
33
     con_fix_percent(con->parent);
34
 
35
@@ -77,8 +78,13 @@ static void attach_to_workspace(Con *con, Con *ws) {
36
 
37
     con->parent = ws;
38
 
39
-    TAILQ_INSERT_TAIL(&(ws->nodes_head), con, nodes);
40
-    TAILQ_INSERT_TAIL(&(ws->focus_head), con, focused);
41
+    if (direction == D_RIGHT || direction == D_DOWN) {
42
+        TAILQ_INSERT_HEAD(&(ws->nodes_head), con, nodes);
43
+        TAILQ_INSERT_HEAD(&(ws->focus_head), con, focused);
44
+    } else {
45
+        TAILQ_INSERT_TAIL(&(ws->nodes_head), con, nodes);
46
+        TAILQ_INSERT_TAIL(&(ws->focus_head), con, focused);
47
+    }
48
 
49
     /* Pretend the con was just opened with regards to size percent values.
50
      * Since the con is moved to a completely different con, the old value
51
@@ -88,6 +94,32 @@ static void attach_to_workspace(Con *con, Con *ws) {
52
 }
53
 
54
 /*
55
+ * Moves the given container to the closest output in the given direction if
56
+ * such an output exists.
57
+ *
58
+ */
59
+static void move_to_output_directed(Con *con, direction_t direction) {
60
+    Con *current_output_con = con_get_output(con);
61
+    Output *current_output = get_output_by_name(current_output_con->name);
62
+    Output *output = get_output_next(direction, current_output, CLOSEST_OUTPUT);
63
+
64
+    if (!output) {
65
+        DLOG("No output in this direction found. Not moving.\n");
66
+        return;
67
+    }
68
+
69
+    Con *ws = NULL;
70
+    GREP_FIRST(ws, output_get_content(output->con), workspace_is_visible(child));
71
+
72
+    if (!ws) {
73
+        DLOG("No workspace on output in this direction found. Not moving.\n");
74
+        return;
75
+    }
76
+
77
+    attach_to_workspace(con, ws, direction);
78
+}
79
+
80
+/*
81
  * Moves the current container in the given direction (D_LEFT, D_RIGHT,
82
  * D_UP, D_DOWN).
83
  *
84
@@ -103,8 +135,9 @@ void tree_move(int direction) {
85
     }
86
 
87
     if (con->parent->type == CT_WORKSPACE && con_num_children(con->parent) == 1) {
88
-        DLOG("This is the only con on this workspace, not doing anything\n");
89
-        return;
90
+        /* This is the only con on this workspace */
91
+        move_to_output_directed(con, direction);
92
+        goto end;
93
     }
94
 
95
     orientation_t o = (direction == D_LEFT || direction == D_RIGHT ? HORIZ : VERT);
96
@@ -124,7 +157,7 @@ void tree_move(int direction) {
97
             if (con_inside_floating(con)) {
98
                 /* 'con' should be moved out of a floating container */
99
                 DLOG("Inside floating, moving to workspace\n");
100
-                attach_to_workspace(con, con_get_workspace(con));
101
+                attach_to_workspace(con, con_get_workspace(con), direction);
102
                 goto end;
103
             }
104
             DLOG("Force-changing orientation\n");
105
@@ -154,12 +187,15 @@ void tree_move(int direction) {
106
                 return;
107
             }
108
 
109
-            /* If there was no con with which we could swap the current one, search
110
-             * again, but starting one level higher. If we are on the workspace
111
-             * level, don’t do that. The result would be a force change of
112
-             * workspace orientation, which is not necessary. */
113
-            if (con->parent == con_get_workspace(con))
114
-                return;
115
+            if (con->parent == con_get_workspace(con)) {
116
+                /*  If we couldn't find a place to move it on this workspace,
117
+                 *  try to move it to a workspace on a different output */
118
+                move_to_output_directed(con, direction);
119
+                goto end;
120
+            }
121
+
122
+            /* If there was no con with which we could swap the current one,
123
+             * search again, but starting one level higher. */
124
             same_orientation = con_parent_with_orientation(con->parent, o);
125
         }
126
     } while (same_orientation == NULL);

b/testcases/t/516-move.t

132
@@ -0,0 +1,106 @@
133
+#!perl
134
+# vim:ts=4:sw=4:expandtab
135
+#
136
+# Please read the following documents before working on tests:
137
+# • http://build.i3wm.org/docs/testsuite.html
138
+#   (or docs/testsuite)
139
+#
140
+# • http://build.i3wm.org/docs/lib-i3test.html
141
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
142
+#
143
+# • http://build.i3wm.org/docs/ipc.html
144
+#   (or docs/ipc)
145
+#
146
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
147
+#   (unless you are already familiar with Perl)
148
+#
149
+# Tests if a simple 'move <direction>' command will move containers across outputs.
150
+#
151
+use i3test i3_autostart => 0;
152
+
153
+# Ensure the pointer is at (0, 0) so that we really start on the first
154
+# (the left) workspace.
155
+$x->root->warp_pointer(0, 0);
156
+
157
+my $config = <<EOT;
158
+# i3 config file (v4)
159
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
160
+
161
+fake-outputs 1024x768+0+0,1024x768+1024+0,1024x768+1024+768,1024x768+0+768
162
+
163
+workspace left-top output fake-0
164
+workspace right-top output fake-1
165
+workspace right-bottom output fake-2
166
+workspace left-bottom output fake-3
167
+EOT
168
+
169
+my $pid = launch_with_config($config);
170
+
171
+#####################################################################
172
+# Try to move a single window across outputs in each direction
173
+#####################################################################
174
+
175
+cmd('workspace left-top');
176
+my $alone_window = open_window;
177
+
178
+cmd('move right');
179
+is(scalar @{get_ws_content('right-top')}, 1, 'moved individual window to right-top workspace');
180
+
181
+cmd('move down');
182
+is(scalar @{get_ws_content('right-bottom')}, 1, 'moved individual window to right-bottom workspace');
183
+
184
+cmd('move left');
185
+is(scalar @{get_ws_content('left-bottom')}, 1, 'moved individual window to left-bottom workspace');
186
+
187
+cmd('move up');
188
+is(scalar @{get_ws_content('left-top')}, 1, 'moved individual window to left-top workspace');
189
+
190
+$alone_window->unmap;
191
+wait_for_unmap;
192
+
193
+#####################################################################
194
+# Try to move a window on a workspace with two windows across outputs in each
195
+# direction
196
+#####################################################################
197
+
198
+# from left-top to right-top
199
+cmd('workspace left-top');
200
+cmd('split h');
201
+my $first_window = open_window;
202
+my $social_window = open_window( name => 'CORRECT_WINDOW' );
203
+cmd('move right');
204
+is(scalar @{get_ws_content('right-top')}, 1, 'moved some window to right-top workspace');
205
+my $compare_window = shift @{get_ws_content('right-top')};
206
+is($compare_window->{name}, $social_window->name, 'moved correct window to right-top workspace');
207
+# unamp the first window so we don't confuse it when we move back here
208
+$first_window->unmap;
209
+wait_for_unmap;
210
+
211
+# from right-top to right-bottom
212
+cmd('split v');
213
+open_window;
214
+# this window opened above - we need to move down twice
215
+cmd('focus up; move down; move down');
216
+is(scalar @{get_ws_content('right-bottom')}, 1, 'moved some window to right-bottom workspace');
217
+$compare_window = shift @{get_ws_content('right-bottom')};
218
+is($compare_window->{name}, $social_window->name, 'moved correct window to right-bottom workspace');
219
+
220
+# from right-bottom to left-bottom
221
+cmd('split h');
222
+open_window;
223
+cmd('focus left; move left');
224
+is(scalar @{get_ws_content('left-bottom')}, 1, 'moved some window to left-bottom workspace');
225
+$compare_window = shift @{get_ws_content('left-bottom')};
226
+is($social_window->name, $compare_window->{name}, 'moved correct window to left-bottom workspace');
227
+
228
+# from left-bottom to left-top
229
+cmd('split v');
230
+open_window;
231
+cmd('focus up; move up');
232
+is(scalar @{get_ws_content('left-top')}, 1, 'moved some window to left-bottom workspace');
233
+$compare_window = shift @{get_ws_content('left-top')};
234
+is($social_window->name, $compare_window->{name}, 'moved correct window to left-bottom workspace');
235
+
236
+exit_gracefully($pid);
237
+
238
+done_testing;