i3 - improved tiling WM


i3bar: implement custom workspace numbers config

Patch status: needinfo

Patch by Tony Crisci

Long description:

Implement the configuration option within the bar config directive for
custom workspace numbers with the directive `workspace_numbers custom`.

This directive strips the workspace name of the number prefix and
delimiter. When the workspace name consists only of the number, it will
default to show the number.

For example:

* "2:5" -> "5"
* "4:$" -> "$"
* "8" -> "8"

This allows customization of i3bar for alternate ordering of workspaces
which has a legitimate use for alternate keyboard layouts such as
Dvorak.

fixes #1131

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

b/docs/userguide

41
@@ -1191,6 +1191,31 @@ bar {
42
 }
43
 ------------------------
44
 
45
+=== Workspace numbers
46
+
47
+Specifies how workspace numbers should be displayed within the workspace
48
+buttons. This is useful if you want to have a named workspace that stays in
49
+order on the bar according to its number without displaying the number prefix.
50
+
51
+When +workspace_numbers+ is set to +custom+, any workspace that has a name of
52
+the form "[n]:[NAME]" will display only the name. You could use this, for
53
+instance, to display Roman numerals rather than digits by naming your
54
+workspaces to "1:I", "2:II", "3:III", "4:IV", ...
55
+
56
+The default is to display the full name within the workspace button.
57
+
58
+*Syntax*:
59
+----------------------------------
60
+workspace_numbers <default|custom>
61
+----------------------------------
62
+
63
+*Example*:
64
+----------------------------
65
+bar {
66
+    workspace_numbers custom
67
+}
68
+----------------------------
69
+
70
 === Binding Mode indicator
71
 
72
 Specifies whether the current binding mode indicator should be shown or not.

b/i3bar/include/config.h

77
@@ -27,6 +27,7 @@ typedef struct config_t {
78
     struct xcb_color_strings_t colors;
79
     bool         disable_binding_mode_indicator;
80
     bool         disable_ws;
81
+    char         *ws_numbers;
82
     char         *bar_id;
83
     char         *command;
84
     char         *fontname;

b/i3bar/include/workspaces.h

89
@@ -31,7 +31,8 @@ void free_workspaces(void);
90
 
91
 struct i3_ws {
92
     int                num;         /* The internal number of the ws */
93
-    i3String           *name;       /* The name of the ws */
94
+    char          *canonical_name;  /* The true name of the ws according to the ipc */
95
+    i3String           *name;       /* The name of the ws that is displayed on the bar */
96
     int                name_width;  /* The rendered width of the name */
97
     bool               visible;     /* If the ws is currently visible on an output */
98
     bool               focused;     /* If the ws is currently focused */

b/i3bar/src/config.c

103
@@ -154,6 +154,14 @@ static int config_string_cb(void *params_, const unsigned char *val, unsigned in
104
         return 1;
105
     }
106
 
107
+    if (!strcmp(cur_key, "workspace_numbers")) {
108
+        DLOG("workspace_numbers = %.*s\n",len, val);
109
+        FREE(config.ws_numbers);
110
+        sasprintf(&config.ws_numbers, "%.*s", len, val);
111
+        return 1;
112
+    }
113
+
114
+
115
 #define COLOR(json_name, struct_name) \
116
     do { \
117
         if (!strcmp(cur_key, #json_name)) { \

b/i3bar/src/workspaces.c

122
@@ -113,14 +113,38 @@ static int workspaces_string_cb(void *params_, const unsigned char *val, unsigne
123
         char *output_name;
124
 
125
         if (!strcmp(params->cur_key, "name")) {
126
-            /* Save the name */
127
-            params->workspaces_walk->name = i3string_from_utf8_with_length((const char *)val, len);
128
+            const char *ws_name = (const char*)val;
129
+            params->workspaces_walk->canonical_name = strndup(ws_name, len);
130
+
131
+            if (config.ws_numbers && strcmp(config.ws_numbers, "custom") == 0) {
132
+                /* Special case: strip off the workspace number */
133
+                static char ws_num[10];
134
+
135
+                snprintf(ws_num, sizeof(ws_num), "%d", atoi(ws_name));
136
+
137
+                /* Calculate the length of the number str in the name */
138
+                int offset = strspn(ws_name, ws_num);
139
+
140
+                /* Also strip off the conventional ws name delimiter */
141
+                if (offset && ws_name[offset] == ':')
142
+                    offset += 1;
143
+
144
+                /* Offset may be equal to length, in which case display the number */
145
+                params->workspaces_walk->name = (offset < len
146
+                        ? i3string_from_utf8_with_length(ws_name + offset, len - offset)
147
+                        : i3string_from_utf8(ws_num));
148
+
149
+            } else {
150
+                /* Default case: just save the name */
151
+                params->workspaces_walk->name = i3string_from_utf8_with_length(ws_name, len);
152
+            }
153
 
154
             /* Save its rendered width */
155
             params->workspaces_walk->name_width =
156
                 predict_text_width(params->workspaces_walk->name);
157
 
158
-            DLOG("Got Workspace %s, name_width: %d, glyphs: %zu\n",
159
+            DLOG("Got Workspace canonical: %s, name: '%s', name_width: %d, glyphs: %zu\n",
160
+                 params->workspaces_walk->canonical_name,
161
                  i3string_as_utf8(params->workspaces_walk->name),
162
                  params->workspaces_walk->name_width,
163
                  i3string_get_num_glyphs(params->workspaces_walk->name));
164
@@ -267,6 +291,7 @@ void free_workspaces(void) {
165
         if (outputs_walk->workspaces != NULL && !TAILQ_EMPTY(outputs_walk->workspaces)) {
166
             TAILQ_FOREACH(ws_walk, outputs_walk->workspaces, tailq) {
167
                 I3STRING_FREE(ws_walk->name);
168
+                FREE(ws_walk->canonical_name);
169
             }
170
             FREE_TAILQ(outputs_walk->workspaces, i3_ws);
171
         }

b/i3bar/src/xcb.c

176
@@ -418,7 +418,7 @@ void handle_button(xcb_button_press_event_t *event) {
177
      * buffer, then we copy character by character. */
178
     int num_quotes = 0;
179
     size_t namelen = 0;
180
-    const char *utf8_name = i3string_as_utf8(cur_ws->name);
181
+    const char *utf8_name = cur_ws->canonical_name;
182
     for (const char *walk = utf8_name; *walk != '\0'; walk++) {
183
         if (*walk == '"')
184
             num_quotes++;

b/include/config.h

189
@@ -267,6 +267,10 @@ struct Barconfig {
190
      * zero. */
191
     bool hide_workspace_buttons;
192
 
193
+    /** Custom workspace numbers? Configuration option is 'workspace_numbers
194
+     * custom'. */
195
+    char *workspace_numbers;
196
+
197
     /** Hide mode button? Configuration option is 'binding_mode_indicator no'
198
      * but we invert the bool for the same reason as hide_workspace_buttons.*/
199
     bool hide_binding_mode_indicator;

b/parser-specs/config.spec

204
@@ -358,6 +358,7 @@ state BAR:
205
   'font'                   -> BAR_FONT
206
   'binding_mode_indicator' -> BAR_BINDING_MODE_INDICATOR
207
   'workspace_buttons'      -> BAR_WORKSPACE_BUTTONS
208
+  'workspace_numbers'      -> BAR_WORKSPACE_NUMBERS
209
   'verbose'                -> BAR_VERBOSE
210
   'colors'                 -> BAR_COLORS_BRACE
211
   '}'
212
@@ -420,6 +421,10 @@ state BAR_WORKSPACE_BUTTONS:
213
   value = word
214
       -> call cfg_bar_workspace_buttons($value); BAR
215
 
216
+state BAR_WORKSPACE_NUMBERS:
217
+  value = word
218
+      -> call cfg_bar_workspace_numbers($value); BAR
219
+
220
 state BAR_VERBOSE:
221
   value = word
222
       -> call cfg_bar_verbose($value); BAR

b/src/config_directives.c

227
@@ -517,6 +517,11 @@ CFGFUN(bar_workspace_buttons, const char *value) {
228
     current_bar.hide_workspace_buttons = !eval_boolstr(value);
229
 }
230
 
231
+CFGFUN(bar_workspace_numbers, const char *value) {
232
+    FREE(current_bar.workspace_numbers);
233
+    current_bar.workspace_numbers = sstrdup(value);
234
+}
235
+
236
 CFGFUN(bar_finish) {
237
     DLOG("\t new bar configuration finished, saving.\n");
238
     /* Generate a unique ID for this bar if not already configured */

b/src/ipc.c

243
@@ -514,6 +514,8 @@ static void dump_bar_config(yajl_gen gen, Barconfig *config) {
244
     ystr("workspace_buttons");
245
     y(bool, !config->hide_workspace_buttons);
246
 
247
+    YSTR_IF_SET(workspace_numbers);
248
+
249
     ystr("binding_mode_indicator");
250
     y(bool, !config->hide_binding_mode_indicator);
251
 

b/testcases/t/201-config-parser.t

256
@@ -627,7 +627,7 @@ EOT
257
 
258
 $expected = <<'EOT';
259
 cfg_bar_output(LVDS-1)
260
-ERROR: CONFIG: Expected one of these tokens: <end>, '#', 'set', 'i3bar_command', 'status_command', 'socket_path', 'mode', 'hidden_state', 'id', 'modifier', 'position', 'output', 'tray_output', 'font', 'binding_mode_indicator', 'workspace_buttons', 'verbose', 'colors', '}'
261
+ERROR: CONFIG: Expected one of these tokens: <end>, '#', 'set', 'i3bar_command', 'status_command', 'socket_path', 'mode', 'hidden_state', 'id', 'modifier', 'position', 'output', 'tray_output', 'font', 'binding_mode_indicator', 'workspace_buttons', 'workspace_numbers', 'verbose', 'colors', '}'
262
 ERROR: CONFIG: (in file <stdin>)
263
 ERROR: CONFIG: Line   1: bar {
264
 ERROR: CONFIG: Line   2:     output LVDS-1