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. 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.
  Input checking for the align option is done in another callback
  function
* 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/444/raw.patch | git am

b/i3status.c

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

b/include/i3status.h

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

b/man/i3status.man

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