i3 - improved tiling WM


Support align and min_width module options

Patch status: needinfo

Patch by Marco Hunsicker

Long description:

This patch enables users to define "align" and "min_width" options
right in the i3status module config sections.

Specifically this patch:
* Adds macros for the two new options that are used in the option
  definitions. As the min_width option can take either a string or a
  number, a custom type has been added along with a corresponding callback
  function that parses the provided value (and provides input validation).
  The align option also uses a callback for input validation
* Expands all module config option definitions to include the new
  options
* Extends the SEC_CLOSE_MAP() macro to generate the JSON for the new
  options as necessary
* Updates the manpage to explain the new options

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

b/i3status.c

29
@@ -11,6 +11,7 @@
30
  * See file LICENSE for license information.
31
  *
32
  */
33
+#include <limits.h>
34
 #include <string.h>
35
 #include <stdio.h>
36
 #include <stdbool.h>
37
@@ -35,6 +36,9 @@
38
 
39
 #define exit_if_null(pointer, ...) { if (pointer == NULL) die(__VA_ARGS__); }
40
 
41
+#define CFG_CUSTOM_ALIGN_OPT \
42
+    CFG_STR_CB("align", NULL, CFGF_NONE, parse_align)
43
+
44
 #define CFG_COLOR_OPTS(good, degraded, bad) \
45
     CFG_STR("color_good", good, CFGF_NONE), \
46
     CFG_STR("color_degraded", degraded, CFGF_NONE), \
47
@@ -42,6 +46,9 @@
48
 
49
 #define CFG_CUSTOM_COLOR_OPTS CFG_COLOR_OPTS(NULL, NULL, NULL)
50
 
51
+#define CFG_CUSTOM_MIN_WIDTH_OPT \
52
+    CFG_PTR_CB("min_width", NULL, CFGF_NONE, parse_min_width, free)
53
+
54
 /* socket file descriptor for general purposes */
55
 int general_socket;
56
 
57
@@ -89,6 +96,45 @@ static char *sstrdup(const char *str) {
58
         return result;
59
 }
60
 
61
+/*
62
+ * Parses the "align" module option (to validate input).
63
+ */
64
+static int parse_align(cfg_t *context, cfg_opt_t *option, const char *value, void *result) {
65
+        if (strcmp(value, "center") != 0 && strcmp(value,"left") != 0 && strcmp(value, "right") != 0)
66
+                die("Invalid alignment attribute found in section %s, line %d: \"%s\"\nValid attributes are: left, center, right\n", context->name, context->line, value);
67
+
68
+        char **cresult = result;
69
+        *cresult = sstrdup(value);
70
+
71
+        return 0;
72
+}
73
+
74
+/*
75
+ * Parses the "min_width" module option whose value can either be a string or an integer.
76
+ */
77
+static int parse_min_width(cfg_t *context, cfg_opt_t *option, const char *value, void *result) {
78
+        char *end;
79
+        long num = strtol(value, &end, 10);
80
+
81
+        if (num < 0)
82
+                die("Invalid min_width attribute found in section %s, line %d: %d\nExpected positive integer or string\n", context->name, context->line, num);
83
+        else if (num == LONG_MIN || num == LONG_MAX || (end && *end != '\0'))
84
+                num = 0;
85
+
86
+        if (strlen(value) == 0)
87
+                die("Empty min_width attribute found in section %s, line %d\nExpected positive integer or non-empty string\n", context->name, context->line);
88
+
89
+        if (strcmp(value, "0") == 0)
90
+                die("Invalid min_width attribute found in section %s, line %d: \"%s\"\nExpected positive integer or string\n", context->name, context->line, value);
91
+
92
+        struct min_width *parsed = scalloc(sizeof(struct min_width));
93
+        parsed->str = sstrdup(value);
94
+        parsed->num = num;
95
+
96
+        *(void **)result = parsed;
97
+
98
+        return 0;
99
+}
100
 
101
 /*
102
  * Validates a color in "#RRGGBB" format
103
@@ -219,35 +265,45 @@ int main(int argc, char *argv[]) {
104
         cfg_opt_t run_watch_opts[] = {
105
                 CFG_STR("pidfile", NULL, CFGF_NONE),
106
                 CFG_STR("format", "%title: %status", CFGF_NONE),
107
+                CFG_CUSTOM_ALIGN_OPT,
108
                 CFG_CUSTOM_COLOR_OPTS,
109
+                CFG_CUSTOM_MIN_WIDTH_OPT,
110
                 CFG_END()
111
         };
112
 
113
         cfg_opt_t path_exists_opts[] = {
114
                 CFG_STR("path", NULL, CFGF_NONE),
115
                 CFG_STR("format", "%title: %status", CFGF_NONE),
116
+                CFG_CUSTOM_ALIGN_OPT,
117
                 CFG_CUSTOM_COLOR_OPTS,
118
+                CFG_CUSTOM_MIN_WIDTH_OPT,
119
                 CFG_END()
120
         };
121
 
122
         cfg_opt_t wireless_opts[] = {
123
                 CFG_STR("format_up", "W: (%quality at %essid, %bitrate) %ip", CFGF_NONE),
124
                 CFG_STR("format_down", "W: down", CFGF_NONE),
125
+                CFG_CUSTOM_ALIGN_OPT,
126
                 CFG_CUSTOM_COLOR_OPTS,
127
+                CFG_CUSTOM_MIN_WIDTH_OPT,
128
                 CFG_END()
129
         };
130
 
131
         cfg_opt_t ethernet_opts[] = {
132
                 CFG_STR("format_up", "E: %ip (%speed)", CFGF_NONE),
133
                 CFG_STR("format_down", "E: down", CFGF_NONE),
134
+                CFG_CUSTOM_ALIGN_OPT,
135
                 CFG_CUSTOM_COLOR_OPTS,
136
+                CFG_CUSTOM_MIN_WIDTH_OPT,
137
                 CFG_END()
138
         };
139
 
140
         cfg_opt_t ipv6_opts[] = {
141
                 CFG_STR("format_up", "%ip", CFGF_NONE),
142
                 CFG_STR("format_down", "no IPv6", CFGF_NONE),
143
+                CFG_CUSTOM_ALIGN_OPT,
144
                 CFG_CUSTOM_COLOR_OPTS,
145
+                CFG_CUSTOM_MIN_WIDTH_OPT,
146
                 CFG_END()
147
         };
148
 
149
@@ -260,35 +316,47 @@ int main(int argc, char *argv[]) {
150
                 CFG_BOOL("last_full_capacity", false, CFGF_NONE),
151
                 CFG_BOOL("integer_battery_capacity", false, CFGF_NONE),
152
                 CFG_BOOL("hide_seconds", false, CFGF_NONE),
153
+                CFG_CUSTOM_ALIGN_OPT,
154
                 CFG_CUSTOM_COLOR_OPTS,
155
+                CFG_CUSTOM_MIN_WIDTH_OPT,
156
                 CFG_END()
157
         };
158
 
159
         cfg_opt_t time_opts[] = {
160
                 CFG_STR("format", "%Y-%m-%d %H:%M:%S", CFGF_NONE),
161
+                CFG_CUSTOM_ALIGN_OPT,
162
+                CFG_CUSTOM_MIN_WIDTH_OPT,
163
                 CFG_END()
164
         };
165
 
166
         cfg_opt_t tztime_opts[] = {
167
                 CFG_STR("format", "%Y-%m-%d %H:%M:%S %Z", CFGF_NONE),
168
                 CFG_STR("timezone", "", CFGF_NONE),
169
+                CFG_CUSTOM_ALIGN_OPT,
170
+                CFG_CUSTOM_MIN_WIDTH_OPT,
171
                 CFG_END()
172
         };
173
 
174
         cfg_opt_t ddate_opts[] = {
175
                 CFG_STR("format", "%{%a, %b %d%}, %Y%N - %H", CFGF_NONE),
176
+                CFG_CUSTOM_ALIGN_OPT,
177
+                CFG_CUSTOM_MIN_WIDTH_OPT,
178
                 CFG_END()
179
         };
180
 
181
         cfg_opt_t load_opts[] = {
182
                 CFG_STR("format", "%1min %5min %15min", CFGF_NONE),
183
                 CFG_FLOAT("max_threshold", 5, CFGF_NONE),
184
+                CFG_CUSTOM_ALIGN_OPT,
185
                 CFG_CUSTOM_COLOR_OPTS,
186
+                CFG_CUSTOM_MIN_WIDTH_OPT,
187
                 CFG_END()
188
         };
189
 
190
         cfg_opt_t usage_opts[] = {
191
                 CFG_STR("format", "%usage", CFGF_NONE),
192
+                CFG_CUSTOM_ALIGN_OPT,
193
+                CFG_CUSTOM_MIN_WIDTH_OPT,
194
                 CFG_END()
195
         };
196
 
197
@@ -296,13 +364,17 @@ int main(int argc, char *argv[]) {
198
                 CFG_STR("format", "%degrees C", CFGF_NONE),
199
                 CFG_STR("path", NULL, CFGF_NONE),
200
                 CFG_INT("max_threshold", 75, CFGF_NONE),
201
+                CFG_CUSTOM_ALIGN_OPT,
202
                 CFG_CUSTOM_COLOR_OPTS,
203
+                CFG_CUSTOM_MIN_WIDTH_OPT,
204
                 CFG_END()
205
         };
206
 
207
         cfg_opt_t disk_opts[] = {
208
                 CFG_STR("format", "%free", CFGF_NONE),
209
                 CFG_STR("prefix_type", "binary", CFGF_NONE),
210
+                CFG_CUSTOM_ALIGN_OPT,
211
+                CFG_CUSTOM_MIN_WIDTH_OPT,
212
                 CFG_END()
213
         };
214
 
215
@@ -312,7 +384,9 @@ int main(int argc, char *argv[]) {
216
                 CFG_STR("device", "default", CFGF_NONE),
217
                 CFG_STR("mixer", "Master", CFGF_NONE),
218
                 CFG_INT("mixer_idx", 0, CFGF_NONE),
219
+                CFG_CUSTOM_ALIGN_OPT,
220
                 CFG_CUSTOM_COLOR_OPTS,
221
+                CFG_CUSTOM_MIN_WIDTH_OPT,
222
                 CFG_END()
223
         };
224
 

b/include/i3status.h

229
@@ -88,6 +88,22 @@ enum { O_DZEN2, O_XMOBAR, O_I3BAR, O_TERM, O_NONE } output_format;
230
 #define SEC_CLOSE_MAP \
231
 	do { \
232
 		if (output_format == O_I3BAR) { \
233
+			char *_align = cfg_getstr(sec, "align"); \
234
+			if (_align) { \
235
+				yajl_gen_string(json_gen, (const unsigned char *)"align", strlen("align")); \
236
+				yajl_gen_string(json_gen, (const unsigned char *)_align, strlen(_align)); \
237
+			} \
238
+			struct min_width *_width = cfg_getptr(sec, "min_width"); \
239
+			if (_width) { \
240
+				/* if the value can be parsed as a number, we use the numerical value */ \
241
+				if (_width->num > 0) { \
242
+					yajl_gen_string(json_gen, (const unsigned char *)"min_width", strlen("min_width")); \
243
+					yajl_gen_integer(json_gen, _width->num); \
244
+				} else { \
245
+					yajl_gen_string(json_gen, (const unsigned char *)"min_width", strlen("min_width")); \
246
+					yajl_gen_string(json_gen, (const unsigned char *)_width->str, strlen(_width->str)); \
247
+				} \
248
+			} \
249
 			const char *_sep = cfg_getstr(cfg_general, "separator"); \
250
 			if (strlen(_sep) == 0) {\
251
 				yajl_gen_string(json_gen, (const unsigned char *)"separator", strlen("separator")); \
252
@@ -132,6 +148,14 @@ enum { O_DZEN2, O_XMOBAR, O_I3BAR, O_TERM, O_NONE } output_format;
253
 
254
 typedef enum { CS_DISCHARGING, CS_CHARGING, CS_FULL } charging_status_t;
255
 
256
+/*
257
+ * The "min_width" module option may either be defined as a string or a number.
258
+ */
259
+struct min_width {
260
+    long num;
261
+    const char *str;
262
+};
263
+
264
 /* src/general.c */
265
 char *skip_character(char *input, char character, int amount);
266
 void die(const char *fmt, ...);

b/man/i3status.man

271
@@ -404,6 +404,35 @@ volume master {
272
 }
273
 -------------------------------------------------------------
274
 
275
+== Universal module options
276
+
277
+When using the i3bar output format, there are a few additional options that
278
+can be used with all modules to customize their appearance:
279
+
280
+align::
281
+	The alignment policy to use when the minimum width (see below) is not
282
+	reached. Either +center+ (default), +right+ or +left+.
283
+min_width::
284
+	The minimum width (in pixels) the module should occupy. If the module takes
285
+	less space than the specified size, the block will be padded to the left
286
+	and/or the right side, according to the defined alignment policy. This is
287
+	useful when you want to prevent the whole status line from shifting when
288
+	values take more or less space between each iteration.
289
+	The option can also be a string. In this case, the width of the given text
290
+	determines the minimum width of the block. This is useful when you want to
291
+	set a sensible minimum width regardless of which font you are using, and at
292
+	what particular size. Please note that a number enclosed with quotes will
293
+	still be treated as a number.
294
+
295
+*Example configuration*:
296
+-------------------------------------------------------------
297
+disk "/" {
298
+    format = "%avail"
299
+    align = "left"
300
+    min_width = 100
301
+}
302
+-------------------------------------------------------------
303
+
304
 == Using i3status with dzen2
305
 
306
 After installing dzen2, you can directly use it with i3status. Just ensure that