i3 - improved tiling WM


Feature: Workspace assignment by number

Patch status: needinfo

Patch by Tony Crisci

Long description:

Workspace assignments with bare numbers assign all workspaces with that
number to the specified output.

Workspace assignment by number is overridden by workspace assignment by
name.

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

b/include/data.h

23
@@ -158,7 +158,7 @@ struct deco_render_params {
24
 };
25
 
26
 /**
27
- * Stores which workspace (by name) goes to which output.
28
+ * Stores which workspace (by name or number) goes to which output.
29
  *
30
  */
31
 struct Workspace_Assignment {

b/include/util.h

36
@@ -59,6 +59,19 @@ bool rect_contains(Rect rect, uint32_t x, uint32_t y);
37
 Rect rect_add(Rect a, Rect b);
38
 
39
 /**
40
+ * Returns true if the name consists of only digits.
41
+ *
42
+ */
43
+bool name_is_digits(char *name);
44
+
45
+/**
46
+ * Parses the workspace name as a number. Returns -1 if the workspace should be
47
+ * interpreted as a "named workspace".
48
+ *
49
+ */
50
+long ws_name_to_number(const char *name);
51
+
52
+/**
53
  * Updates *destination with new_value and returns true if it was changed or false
54
  * if it was the same
55
  *

b/src/util.c

60
@@ -21,6 +21,7 @@
61
 #include <pwd.h>
62
 #include <yajl/yajl_version.h>
63
 #include <libgen.h>
64
+#include <ctype.h>
65
 
66
 #define SN_API_NOT_YET_FROZEN 1
67
 #include <libsn/sn-launcher.h>
68
@@ -48,6 +49,38 @@ Rect rect_add(Rect a, Rect b) {
69
 }
70
 
71
 /*
72
+ * Returns true if the name consists of only digits.
73
+ *
74
+ */
75
+bool name_is_digits(char *name) {
76
+    /* positive integers and zero are interpreted as numbers */
77
+    for (int i = 0; i < strlen(name); i++)
78
+        if (!isdigit(name[i]))
79
+            return false;
80
+
81
+    return true;
82
+}
83
+
84
+/*
85
+ * Parses the workspace name as a number. Returns -1 if the workspace should be
86
+ * interpreted as a "named workspace".
87
+ *
88
+ */
89
+long ws_name_to_number(const char *name) {
90
+    /* positive integers and zero are interpreted as numbers */
91
+    char *endptr = NULL;
92
+    long parsed_num = strtol(name, &endptr, 10);
93
+    if (parsed_num == LONG_MIN ||
94
+            parsed_num == LONG_MAX ||
95
+            parsed_num < 0 ||
96
+            endptr == name) {
97
+        parsed_num = -1;
98
+    }
99
+
100
+    return parsed_num;
101
+}
102
+
103
+/*
104
  * Updates *destination with new_value and returns true if it was changed or false
105
  * if it was the same
106
  *

b/src/workspace.c

111
@@ -53,14 +53,25 @@ Con *workspace_get(const char *num, bool *created) {
112
         output = con_get_output(focused);
113
         /* look for assignments */
114
         struct Workspace_Assignment *assignment;
115
-        TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
116
-            if (strcmp(assignment->name, num) != 0)
117
-                continue;
118
 
119
-            LOG("Found workspace assignment to output \"%s\"\n", assignment->output);
120
-            GREP_FIRST(output, croot, !strcmp(child->name, assignment->output));
121
-            break;
122
+        /* We set workspace->num to the number if this workspace’s name begins
123
+         * with a positive number. Otherwise it’s a named ws and num will be
124
+         * -1. */
125
+        long parsed_num = ws_name_to_number(num);
126
+
127
+        TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
128
+            if (strcmp(assignment->name, num) == 0) {
129
+                DLOG("Found workspace name assignment to output \"%s\"\n", assignment->output);
130
+                GREP_FIRST(output, croot, !strcmp(child->name, assignment->output));
131
+                break;
132
+            } else if (parsed_num != -1
133
+                    && name_is_digits(assignment->name)
134
+                    && ws_name_to_number(assignment->name) == parsed_num) {
135
+                DLOG("Found workspace number assignment to output \"%s\"\n", assignment->output);
136
+                GREP_FIRST(output, croot, !strcmp(child->name, assignment->output));
137
+            }
138
         }
139
+
140
         Con *content = output_get_content(output);
141
         LOG("got output %p with content %p\n", output, content);
142
         /* We need to attach this container after setting its type. con_attach
143
@@ -74,16 +85,7 @@ Con *workspace_get(const char *num, bool *created) {
144
         FREE(workspace->name);
145
         workspace->name = sstrdup(num);
146
         workspace->workspace_layout = config.default_layout;
147
-        /* We set ->num to the number if this workspace’s name begins with a
148
-         * positive number. Otherwise it’s a named ws and num will be -1. */
149
-        char *endptr = NULL;
150
-        long parsed_num = strtol(num, &endptr, 10);
151
-        if (parsed_num == LONG_MIN ||
152
-            parsed_num == LONG_MAX ||
153
-            parsed_num < 0 ||
154
-            endptr == num)
155
-            workspace->num = -1;
156
-        else workspace->num = parsed_num;
157
+        workspace->num = parsed_num;
158
         LOG("num = %d\n", workspace->num);
159
 
160
         workspace->parent = content;

b/testcases/t/518-interpret-workspace-numbers.t

166
@@ -0,0 +1,77 @@
167
+#!perl
168
+# vim:ts=4:sw=4:expandtab
169
+#
170
+# Please read the following documents before working on tests:
171
+# • http://build.i3wm.org/docs/testsuite.html
172
+#   (or docs/testsuite)
173
+#
174
+# • http://build.i3wm.org/docs/lib-i3test.html
175
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
176
+#
177
+# • http://build.i3wm.org/docs/ipc.html
178
+#   (or docs/ipc)
179
+#
180
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
181
+#   (unless you are already familiar with Perl)
182
+#
183
+# Tests that workspace assignment config directives for plain numbers will
184
+# assign any workspace of that number to the specified output.
185
+# Ticket: #1238
186
+# Bug still in: 4.7.2-147-g3760a48
187
+use i3test i3_autostart => 0;
188
+
189
+my $config = <<EOT;
190
+# i3 config file (v4)
191
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
192
+
193
+workspace 1:override output fake-0
194
+workspace 2 output fake-0
195
+workspace 1 output fake-1
196
+workspace 2:override output fake-1
197
+
198
+fake-outputs 1024x768+0+0,1024x768+1024+0
199
+EOT
200
+
201
+my $pid = launch_with_config($config);
202
+my $i3 = i3(get_socket_path());
203
+$i3->connect->recv;
204
+
205
+# Returns the name of the output on which this workspace resides
206
+sub get_output_for_workspace {
207
+    my $ws_name = shift @_;
208
+
209
+    foreach (grep { not $_->{name} =~ /^__/ } @{$i3->get_tree->recv->{nodes}}) {
210
+        my $output = $_->{name};
211
+        foreach (grep { $_->{name} =~ "content" } @{$_->{nodes}}) {
212
+            return $output if $_->{nodes}[0]->{name} =~ $ws_name;
213
+        }
214
+    }
215
+}
216
+
217
+################################################################################
218
+# Workspace assignments with bare numbers should be interpreted as `workspace
219
+# number` config directives. Any workspace beginning with that number should be
220
+# assigned to the specified output.
221
+################################################################################
222
+
223
+cmd 'focus output fake-1';
224
+cmd 'workspace "2:foo"';
225
+is(get_output_for_workspace('2:foo'), 'fake-0',
226
+    'Workspaces should be assigned by number when the assignment is a plain number')
227
+    or diag 'Since workspace number 2 is assigned to fake-0, 2:foo should open on fake-0';
228
+
229
+cmd 'focus output fake-0';
230
+cmd 'workspace "2:override"';
231
+is(get_output_for_workspace('2:override'), 'fake-1',
232
+    'Workspace assignments by name should override numbered assignments')
233
+    or diag 'Since workspace "2:override" is assigned by name to fake-1, it should open on fake-1';
234
+
235
+cmd 'focus output fake-1';
236
+cmd 'workspace "1:override"';
237
+is(get_output_for_workspace('1:override'), 'fake-0',
238
+    'Assignment rules should not be affected by the order assignments are declared')
239
+    or diag 'Since workspace "1:override" is assigned by name to fake-0, it should open on fake-0';
240
+
241
+exit_gracefully($pid);
242
+
243
+done_testing;