i3 - improved tiling WM


move scratchpad on a scratchpad windows inserts in head

Patch status: rejected

Patch by Philippe Virouleau

Long description:

Implement the behaviour described in #1035 : 'scratchpad show' cycles
between windows, 'scratchpad move' on a scratchpad window makes it the
next to be displayed by 'scratchpad show'.

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

b/include/data.h

23
@@ -585,7 +585,11 @@ struct Con {
24
         SCRATCHPAD_FRESH = 1,
25
 
26
         /* The user changed position/size of the scratchpad window. */
27
-        SCRATCHPAD_CHANGED = 2
28
+        SCRATCHPAD_CHANGED = 2,
29
+
30
+        /* A scratchpad window moving to scratchpad again through an
31
+         * actual 'scratchpad move'. */
32
+        SCRATCHPAD_MOVE = 3
33
     } scratchpad_state;
34
 
35
     /* The ID of this container before restarting. Necessary to correctly

b/include/scratchpad.h

40
@@ -17,7 +17,7 @@
41
  * Gets called upon the command 'move scratchpad'.
42
  *
43
  */
44
-void scratchpad_move(Con *con);
45
+void scratchpad_move(Con *con, bool from_show);
46
 
47
 /**
48
  * Either shows the top-most scratchpad window (con == NULL) or shows the

b/src/commands.c

53
@@ -1786,7 +1786,7 @@ void cmd_move_scratchpad(I3_CMD) {
54
 
55
     TAILQ_FOREACH(current, &owindows, owindows) {
56
         DLOG("matching: %p / %s\n", current->con, current->con->name);
57
-        scratchpad_move(current->con);
58
+        scratchpad_move(current->con, false);
59
     }
60
 
61
     cmd_output->needs_tree_render = true;

b/src/con.c

66
@@ -138,7 +138,12 @@ void con_attach(Con *con, Con *parent, bool ignore_focus) {
67
 
68
     if (con->type == CT_FLOATING_CON) {
69
         DLOG("Inserting into floating containers\n");
70
-        TAILQ_INSERT_TAIL(&(parent->floating_head), con, floating_windows);
71
+        Con *floating;
72
+        if ((floating = con_inside_floating(con))
73
+                && floating->scratchpad_state == SCRATCHPAD_MOVE)
74
+            TAILQ_INSERT_HEAD(&(parent->floating_head), con, floating_windows);
75
+        else
76
+            TAILQ_INSERT_TAIL(&(parent->floating_head), con, floating_windows);
77
     } else {
78
         if (!ignore_focus) {
79
             /* Get the first tiling container in focus stack */

b/src/ipc.c

84
@@ -176,6 +176,9 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
85
         case SCRATCHPAD_CHANGED:
86
             ystr("changed");
87
             break;
88
+        case SCRATCHPAD_MOVE:
89
+            ystr("move");
90
+            break;
91
     }
92
 
93
     ystr("percent");

b/src/load_layout.c

98
@@ -254,6 +254,8 @@ static int json_string(void *ctx, const unsigned char *val, unsigned int len) {
99
                 json_node->scratchpad_state = SCRATCHPAD_FRESH;
100
             else if (strcasecmp(buf, "changed") == 0)
101
                 json_node->scratchpad_state = SCRATCHPAD_CHANGED;
102
+            else if (strcasecmp(buf, "move") == 0)
103
+                json_node->scratchpad_state = SCRATCHPAD_MOVE;
104
             free(buf);
105
         }
106
     }

b/src/scratchpad.c

111
@@ -18,7 +18,7 @@
112
  * Gets called upon the command 'move scratchpad'.
113
  *
114
  */
115
-void scratchpad_move(Con *con) {
116
+void scratchpad_move(Con *con, bool from_show) {
117
     if (con->type == CT_WORKSPACE) {
118
         LOG("'move scratchpad' used on a workspace \"%s\". Calling it "
119
             "recursively on all windows on this workspace.\n", con->name);
120
@@ -26,7 +26,7 @@ void scratchpad_move(Con *con) {
121
         current = TAILQ_FIRST(&(con->focus_head));
122
         while (current) {
123
             Con *next = TAILQ_NEXT(current, focused);
124
-            scratchpad_move(current);
125
+            scratchpad_move(current, from_show);
126
             current = next;
127
         }
128
         return;
129
@@ -57,10 +57,21 @@ void scratchpad_move(Con *con) {
130
         con = maybe_floating_con;
131
     }
132
 
133
+    /*
134
+     * If this is an actual 'scratchpad move' and the window is a scratchpad window,
135
+     * set the scratchpad_state to SCRATCHPAD_MOVE so that it's moved to
136
+     * top of the queue
137
+     */
138
+    int tmp_state = con->scratchpad_state;
139
+    if (!from_show && con->scratchpad_state != SCRATCHPAD_NONE)
140
+        con->scratchpad_state = SCRATCHPAD_MOVE;
141
+
142
     /* 2: Send the window to the __i3_scratch workspace, mainting its
143
      * coordinates and not warping the pointer. */
144
     con_move_to_workspace(con, __i3_scratch, true, true);
145
 
146
+    con->scratchpad_state = tmp_state;
147
+
148
     /* 3: If this is the first time this window is used as a scratchpad, we set
149
      * the scratchpad_state to SCRATCHPAD_FRESH. The window will then be
150
      * adjusted in size according to what the user specifies. */
151
@@ -97,7 +108,7 @@ void scratchpad_show(Con *con) {
152
         (floating = con_inside_floating(focused)) &&
153
         floating->scratchpad_state != SCRATCHPAD_NONE) {
154
         DLOG("Focused window is a scratchpad window, hiding it.\n");
155
-        scratchpad_move(focused);
156
+        scratchpad_move(focused, true);
157
         return;
158
     }
159
 
160
@@ -165,7 +176,7 @@ void scratchpad_show(Con *con) {
161
          * it, otherwise we should move it to the active workspace. */
162
         if (current == active) {
163
             DLOG("Window is a scratchpad window, hiding it.\n");
164
-            scratchpad_move(con);
165
+            scratchpad_move(con, true);
166
             return;
167
         }
168
     }

b/testcases/t/185-scratchpad.t

173
@@ -398,10 +398,11 @@ sub verify_scratchpad_move_with_visible_scratch_con {
174
     # hide window 1 again
175
     cmd 'move scratchpad';
176
 
177
-    # this should bring up window 2
178
+    # Since #1035
179
+    # this should bring up window 1
180
     cmd "workspace $first";
181
     cmd 'scratchpad show';
182
-    is($x->input_focus, $window2->id, "showed the correct scratchpad window");
183
+    is($x->input_focus, $window1->id, "showed the correct scratchpad window");
184
 }
185
 
186
 # let's clear the scratchpad first
187
@@ -428,21 +429,91 @@ does_i3_live;
188
 # when another window on the same workspace has focus
189
 ################################################################################
190
 
191
+sub test_scratchpad_show_moves_focus {
192
+    my $ws = fresh_workspace;
193
+    cmd "workspace $ws";
194
+
195
+    open_window;
196
+    my $scratch = get_focused($ws);
197
+    cmd 'move scratchpad';
198
+    cmd 'scratchpad show';
199
+
200
+    open_window;
201
+    my $not_scratch = get_focused($ws);
202
+    is(get_focused($ws), $not_scratch, 'not scratch window has focus');
203
+
204
+    cmd 'scratchpad show';
205
+
206
+    is(get_focused($ws), $scratch, 'scratchpad is focused');
207
+
208
+    #Kill the scratchpad window so that it doesn't interfere with other tests
209
+    cmd 'kill';
210
+
211
+    is(get_focused($ws), $not_scratch, 'not scratch window has focus');
212
+}
213
+
214
 clear_scratchpad;
215
-my $ws = fresh_workspace;
216
+is (scalar @{get_ws('__i3_scratch')->{floating_nodes}}, 0, "scratchpad is empty");
217
+test_scratchpad_show_moves_focus;
218
 
219
-open_window;
220
-my $scratch = get_focused($ws);
221
-cmd 'move scratchpad';
222
-cmd 'scratchpad show';
223
+################################################################################
224
+# 13bis: Test the following behaviour :
225
+# With multiple windows in the scratchpad, 'scratchpad_show' cycles between
226
+# them, and 'scratchpad move' on a scratchpad window makes it the next to be
227
+# displayed with 'scratchpad show'
228
+################################################################################
229
 
230
-open_window;
231
-my $not_scratch = get_focused($ws);
232
-is(get_focused($ws), $not_scratch, 'not scratch window has focus');
233
+sub test_scratchpad_move_to_head {
234
 
235
-cmd 'scratchpad show';
236
+    my $ws = fresh_workspace;
237
+    cmd "workspace $ws";
238
+
239
+    my $window1 = open_window;
240
+    cmd 'move scratchpad';
241
+
242
+    my $window2 = open_window;
243
+    cmd 'move scratchpad';
244
 
245
-is(get_focused($ws), $scratch, 'scratchpad is focused');
246
+    # this should bring up window 1
247
+    cmd 'scratchpad show';
248
+
249
+    is(scalar @{get_ws($ws)->{floating_nodes}}, 1, 'one floating node on ws');
250
+    is($x->input_focus, $window1->id, "showed the correct scratchpad window1");
251
+
252
+    #this should hide window 1
253
+    cmd 'scratchpad show';
254
+
255
+    is(scalar @{get_ws($ws)->{floating_nodes}}, 0, 'no floating node on ws');
256
+
257
+    #this should show window 2
258
+    cmd 'scratchpad show';
259
+
260
+    is(scalar @{get_ws($ws)->{floating_nodes}}, 1, 'one floating node on ws');
261
+    is($x->input_focus, $window2->id, "showed the correct scratchpad window2");
262
+
263
+    #this should hide window 2 and make it the next window to be displayed
264
+    cmd 'move scratchpad';
265
+
266
+
267
+    is(scalar @{get_ws($ws)->{floating_nodes}}, 0, 'no floating node on ws');
268
+
269
+    #this should show window 2 (#1035)
270
+    cmd 'scratchpad show';
271
+
272
+    is(scalar @{get_ws($ws)->{floating_nodes}}, 1, 'one floating node on ws');
273
+    is($x->input_focus, $window2->id, "showed the correct scratchpad window2");
274
+
275
+    #clean
276
+    cmd 'kill';
277
+    cmd 'scratchpad show';
278
+    cmd 'kill';
279
+
280
+
281
+}
282
+
283
+clear_scratchpad;
284
+is (scalar @{get_ws('__i3_scratch')->{floating_nodes}}, 0, "scratchpad is empty");
285
+test_scratchpad_move_to_head;
286
 
287
 # TODO: make i3bar display *something* when a window on the scratchpad has the urgency hint
288