Support align and min_width module options
Patch status: merged
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/448/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,54 @@ 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 (strcasecmp(value, "left") != 0 && strcasecmp(value,"right") != 0 && strcasecmp(value, "center") != 0) |
| 66 |
+ die("Invalid alignment attribute found in section %s, line %d: \"%s\"\n"
|
| 67 |
+ "Valid attributes are: left, center, right\n", context->name, context->line, value); |
| 68 |
+ |
| 69 |
+ char **cresult = result; |
| 70 |
+ *cresult = sstrdup(value); |
| 71 |
+ |
| 72 |
+ return 0; |
| 73 |
+} |
| 74 |
+ |
| 75 |
+/* |
| 76 |
+ * Parses the "min_width" module option whose value can either be a string or an integer. |
| 77 |
+ */ |
| 78 |
+static int parse_min_width(cfg_t *context, cfg_opt_t *option, const char *value, void *result) {
|
| 79 |
+ char *end; |
| 80 |
+ long num = strtol(value, &end, 10); |
| 81 |
+ |
| 82 |
+ if (num < 0) |
| 83 |
+ die("Invalid min_width attribute found in section %s, line %d: %d\n"
|
| 84 |
+ "Expected positive integer or string\n", context->name, context->line, num); |
| 85 |
+ else if (num == LONG_MIN || num == LONG_MAX || (end && *end != '\0')) |
| 86 |
+ num = 0; |
| 87 |
+ |
| 88 |
+ if (strlen(value) == 0) |
| 89 |
+ die("Empty min_width attribute found in section %s, line %d\n"
|
| 90 |
+ "Expected positive integer or non-empty string\n", context->name, context->line); |
| 91 |
+ |
| 92 |
+ if (strcmp(value, "0") == 0) |
| 93 |
+ die("Invalid min_width attribute found in section %s, line %d: \"%s\"\n"
|
| 94 |
+ "Expected positive integer or string\n", context->name, context->line, value); |
| 95 |
+ |
| 96 |
+ struct min_width *parsed = scalloc(sizeof(struct min_width)); |
| 97 |
+ parsed->num = num; |
| 98 |
+ |
| 99 |
+ /* num is preferred, but if it’s 0 (i.e. not valid), store and use |
| 100 |
+ * the raw string value */ |
| 101 |
+ if (num == 0) |
| 102 |
+ parsed->str = sstrdup(value); |
| 103 |
+ |
| 104 |
+ struct min_width **cresult = result; |
| 105 |
+ *cresult = parsed; |
| 106 |
+ |
| 107 |
+ return 0; |
| 108 |
+} |
| 109 |
|
| 110 |
/* |
| 111 |
* Validates a color in "#RRGGBB" format |
| 112 |
@@ -219,35 +274,45 @@ int main(int argc, char *argv[]) {
|
| 113 |
cfg_opt_t run_watch_opts[] = {
|
| 114 |
CFG_STR("pidfile", 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 path_exists_opts[] = {
|
| 123 |
CFG_STR("path", NULL, CFGF_NONE),
|
| 124 |
CFG_STR("format", "%title: %status", 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 wireless_opts[] = {
|
| 132 |
CFG_STR("format_up", "W: (%quality at %essid, %bitrate) %ip", CFGF_NONE),
|
| 133 |
CFG_STR("format_down", "W: 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 ethernet_opts[] = {
|
| 141 |
CFG_STR("format_up", "E: %ip (%speed)", CFGF_NONE),
|
| 142 |
CFG_STR("format_down", "E: down", CFGF_NONE),
|
| 143 |
+ CFG_CUSTOM_ALIGN_OPT, |
| 144 |
CFG_CUSTOM_COLOR_OPTS, |
| 145 |
+ CFG_CUSTOM_MIN_WIDTH_OPT, |
| 146 |
CFG_END() |
| 147 |
}; |
| 148 |
|
| 149 |
cfg_opt_t ipv6_opts[] = {
|
| 150 |
CFG_STR("format_up", "%ip", CFGF_NONE),
|
| 151 |
CFG_STR("format_down", "no IPv6", CFGF_NONE),
|
| 152 |
+ CFG_CUSTOM_ALIGN_OPT, |
| 153 |
CFG_CUSTOM_COLOR_OPTS, |
| 154 |
+ CFG_CUSTOM_MIN_WIDTH_OPT, |
| 155 |
CFG_END() |
| 156 |
}; |
| 157 |
|
| 158 |
@@ -260,35 +325,47 @@ int main(int argc, char *argv[]) {
|
| 159 |
CFG_BOOL("last_full_capacity", false, CFGF_NONE),
|
| 160 |
CFG_BOOL("integer_battery_capacity", false, CFGF_NONE),
|
| 161 |
CFG_BOOL("hide_seconds", false, CFGF_NONE),
|
| 162 |
+ CFG_CUSTOM_ALIGN_OPT, |
| 163 |
CFG_CUSTOM_COLOR_OPTS, |
| 164 |
+ CFG_CUSTOM_MIN_WIDTH_OPT, |
| 165 |
CFG_END() |
| 166 |
}; |
| 167 |
|
| 168 |
cfg_opt_t time_opts[] = {
|
| 169 |
CFG_STR("format", "%Y-%m-%d %H:%M:%S", CFGF_NONE),
|
| 170 |
+ CFG_CUSTOM_ALIGN_OPT, |
| 171 |
+ CFG_CUSTOM_MIN_WIDTH_OPT, |
| 172 |
CFG_END() |
| 173 |
}; |
| 174 |
|
| 175 |
cfg_opt_t tztime_opts[] = {
|
| 176 |
CFG_STR("format", "%Y-%m-%d %H:%M:%S %Z", CFGF_NONE),
|
| 177 |
CFG_STR("timezone", "", CFGF_NONE),
|
| 178 |
+ CFG_CUSTOM_ALIGN_OPT, |
| 179 |
+ CFG_CUSTOM_MIN_WIDTH_OPT, |
| 180 |
CFG_END() |
| 181 |
}; |
| 182 |
|
| 183 |
cfg_opt_t ddate_opts[] = {
|
| 184 |
CFG_STR("format", "%{%a, %b %d%}, %Y%N - %H", CFGF_NONE),
|
| 185 |
+ CFG_CUSTOM_ALIGN_OPT, |
| 186 |
+ CFG_CUSTOM_MIN_WIDTH_OPT, |
| 187 |
CFG_END() |
| 188 |
}; |
| 189 |
|
| 190 |
cfg_opt_t load_opts[] = {
|
| 191 |
CFG_STR("format", "%1min %5min %15min", CFGF_NONE),
|
| 192 |
CFG_FLOAT("max_threshold", 5, CFGF_NONE),
|
| 193 |
+ CFG_CUSTOM_ALIGN_OPT, |
| 194 |
CFG_CUSTOM_COLOR_OPTS, |
| 195 |
+ CFG_CUSTOM_MIN_WIDTH_OPT, |
| 196 |
CFG_END() |
| 197 |
}; |
| 198 |
|
| 199 |
cfg_opt_t usage_opts[] = {
|
| 200 |
CFG_STR("format", "%usage", CFGF_NONE),
|
| 201 |
+ CFG_CUSTOM_ALIGN_OPT, |
| 202 |
+ CFG_CUSTOM_MIN_WIDTH_OPT, |
| 203 |
CFG_END() |
| 204 |
}; |
| 205 |
|
| 206 |
@@ -296,13 +373,17 @@ int main(int argc, char *argv[]) {
|
| 207 |
CFG_STR("format", "%degrees C", CFGF_NONE),
|
| 208 |
CFG_STR("path", NULL, CFGF_NONE),
|
| 209 |
CFG_INT("max_threshold", 75, CFGF_NONE),
|
| 210 |
+ CFG_CUSTOM_ALIGN_OPT, |
| 211 |
CFG_CUSTOM_COLOR_OPTS, |
| 212 |
+ CFG_CUSTOM_MIN_WIDTH_OPT, |
| 213 |
CFG_END() |
| 214 |
}; |
| 215 |
|
| 216 |
cfg_opt_t disk_opts[] = {
|
| 217 |
CFG_STR("format", "%free", CFGF_NONE),
|
| 218 |
CFG_STR("prefix_type", "binary", CFGF_NONE),
|
| 219 |
+ CFG_CUSTOM_ALIGN_OPT, |
| 220 |
+ CFG_CUSTOM_MIN_WIDTH_OPT, |
| 221 |
CFG_END() |
| 222 |
}; |
| 223 |
|
| 224 |
@@ -312,7 +393,9 @@ int main(int argc, char *argv[]) {
|
| 225 |
CFG_STR("device", "default", CFGF_NONE),
|
| 226 |
CFG_STR("mixer", "Master", CFGF_NONE),
|
| 227 |
CFG_INT("mixer_idx", 0, CFGF_NONE),
|
| 228 |
+ CFG_CUSTOM_ALIGN_OPT, |
| 229 |
CFG_CUSTOM_COLOR_OPTS, |
| 230 |
+ CFG_CUSTOM_MIN_WIDTH_OPT, |
| 231 |
CFG_END() |
| 232 |
}; |
| 233 |
|
b/include/i3status.h
| 238 |
@@ -88,6 +88,22 @@ enum { O_DZEN2, O_XMOBAR, O_I3BAR, O_TERM, O_NONE } output_format;
|
| 239 |
#define SEC_CLOSE_MAP \ |
| 240 |
do { \
|
| 241 |
if (output_format == O_I3BAR) { \
|
| 242 |
+ char *_align = cfg_getstr(sec, "align"); \ |
| 243 |
+ if (_align) { \
|
| 244 |
+ yajl_gen_string(json_gen, (const unsigned char *)"align", strlen("align")); \
|
| 245 |
+ yajl_gen_string(json_gen, (const unsigned char *)_align, strlen(_align)); \ |
| 246 |
+ } \ |
| 247 |
+ struct min_width *_width = cfg_getptr(sec, "min_width"); \ |
| 248 |
+ if (_width) { \
|
| 249 |
+ /* if the value can be parsed as a number, we use the numerical value */ \ |
| 250 |
+ if (_width->num > 0) { \
|
| 251 |
+ yajl_gen_string(json_gen, (const unsigned char *)"min_width", strlen("min_width")); \
|
| 252 |
+ yajl_gen_integer(json_gen, _width->num); \ |
| 253 |
+ } else { \
|
| 254 |
+ yajl_gen_string(json_gen, (const unsigned char *)"min_width", strlen("min_width")); \
|
| 255 |
+ yajl_gen_string(json_gen, (const unsigned char *)_width->str, strlen(_width->str)); \ |
| 256 |
+ } \ |
| 257 |
+ } \ |
| 258 |
const char *_sep = cfg_getstr(cfg_general, "separator"); \ |
| 259 |
if (strlen(_sep) == 0) {\
|
| 260 |
yajl_gen_string(json_gen, (const unsigned char *)"separator", strlen("separator")); \
|
| 261 |
@@ -132,6 +148,14 @@ enum { O_DZEN2, O_XMOBAR, O_I3BAR, O_TERM, O_NONE } output_format;
|
| 262 |
|
| 263 |
typedef enum { CS_DISCHARGING, CS_CHARGING, CS_FULL } charging_status_t;
|
| 264 |
|
| 265 |
+/* |
| 266 |
+ * The "min_width" module option may either be defined as a string or a number. |
| 267 |
+ */ |
| 268 |
+struct min_width {
|
| 269 |
+ long num; |
| 270 |
+ const char *str; |
| 271 |
+}; |
| 272 |
+ |
| 273 |
/* src/general.c */ |
| 274 |
char *skip_character(char *input, char character, int amount); |
| 275 |
void die(const char *fmt, ...); |
b/man/i3status.man
| 280 |
@@ -404,6 +404,35 @@ volume master {
|
| 281 |
} |
| 282 |
------------------------------------------------------------- |
| 283 |
|
| 284 |
+== Universal module options |
| 285 |
+ |
| 286 |
+When using the i3bar output format, there are a few additional options that |
| 287 |
+can be used with all modules to customize their appearance: |
| 288 |
+ |
| 289 |
+align:: |
| 290 |
+ The alignment policy to use when the minimum width (see below) is not |
| 291 |
+ reached. Either +center+ (default), +right+ or +left+. |
| 292 |
+min_width:: |
| 293 |
+ The minimum width (in pixels) the module should occupy. If the module takes |
| 294 |
+ less space than the specified size, the block will be padded to the left |
| 295 |
+ and/or the right side, according to the defined alignment policy. This is |
| 296 |
+ useful when you want to prevent the whole status line from shifting when |
| 297 |
+ values take more or less space between each iteration. |
| 298 |
+ The option can also be a string. In this case, the width of the given text |
| 299 |
+ determines the minimum width of the block. This is useful when you want to |
| 300 |
+ set a sensible minimum width regardless of which font you are using, and at |
| 301 |
+ what particular size. Please note that a number enclosed with quotes will |
| 302 |
+ still be treated as a number. |
| 303 |
+ |
| 304 |
+*Example configuration*: |
| 305 |
+------------------------------------------------------------- |
| 306 |
+disk "/" {
|
| 307 |
+ format = "%avail" |
| 308 |
+ align = "left" |
| 309 |
+ min_width = 100 |
| 310 |
+} |
| 311 |
+------------------------------------------------------------- |
| 312 |
+ |
| 313 |
== Using i3status with dzen2 |
| 314 |
|
| 315 |
After installing dzen2, you can directly use it with i3status. Just ensure that |