From dcd0518e25d7aa84a720780cb70b3f8fca867972 Mon Sep 17 00:00:00 2001
From: Kenneth Lyons <ixjlyons@gmail.com>
Date: Mon, 5 Oct 2015 01:10:01 -0700
Subject: Added support for Pango markup.

---
 i3status.c                | 14 ++++++++++++--
 include/i3status.h        |  9 ++++++++-
 man/i3status.man          | 19 +++++++++++++++++++
 src/output.c              | 43 +++++++++++++++++++++++++++++++++++++++++++
 src/print_time.c          | 25 ++++++++++++++++++++++---
 src/print_wireless_info.c |  2 +-
 6 files changed, 105 insertions(+), 7 deletions(-)

diff --git a/i3status.c b/i3status.c
index 462721b..3781425 100644
--- a/i3status.c
+++ b/i3status.c
@@ -295,6 +295,7 @@ int main(int argc, char *argv[]) {
         CFG_STR("color_separator", "#333333", CFGF_NONE),
         CFG_INT("interval", 1, CFGF_NONE),
         CFG_COLOR_OPTS("#00FF00", "#FFFF00", "#FF0000"),
+        CFG_STR("markup", "none", CFGF_NONE),
         CFG_END()};
 
     cfg_opt_t run_watch_opts[] = {
@@ -365,6 +366,7 @@ int main(int argc, char *argv[]) {
     cfg_opt_t tztime_opts[] = {
         CFG_STR("format", "%Y-%m-%d %H:%M:%S %Z", CFGF_NONE),
         CFG_STR("timezone", "", CFGF_NONE),
+        CFG_STR("format_time", NULL, CFGF_NONE),
         CFG_CUSTOM_ALIGN_OPT,
         CFG_CUSTOM_MIN_WIDTH_OPT,
         CFG_END()};
@@ -532,6 +534,14 @@ int main(int argc, char *argv[]) {
     if (!valid_color(cfg_getstr(cfg_general, "color_good")) || !valid_color(cfg_getstr(cfg_general, "color_degraded")) || !valid_color(cfg_getstr(cfg_general, "color_bad")) || !valid_color(cfg_getstr(cfg_general, "color_separator")))
         die("Bad color format");
 
+    char *markup_str = cfg_getstr(cfg_general, "markup");
+    if (strcasecmp(markup_str, "pango") == 0)
+        markup_format = M_PANGO;
+    else if (strcasecmp(markup_str, "none") == 0)
+        markup_format = M_NONE;
+    else
+        die("Unknown markup format: \"%s\"\n", markup_str);
+
 #if YAJL_MAJOR >= 2
     yajl_gen json_gen = yajl_gen_alloc(NULL);
 #else
@@ -648,13 +658,13 @@ int main(int argc, char *argv[]) {
 
             CASE_SEC("time") {
                 SEC_OPEN_MAP("time");
-                print_time(json_gen, buffer, NULL, cfg_getstr(sec, "format"), NULL, tv.tv_sec);
+                print_time(json_gen, buffer, NULL, cfg_getstr(sec, "format"), NULL, NULL, tv.tv_sec);
                 SEC_CLOSE_MAP;
             }
 
             CASE_SEC_TITLE("tztime") {
                 SEC_OPEN_MAP("tztime");
-                print_time(json_gen, buffer, title, cfg_getstr(sec, "format"), cfg_getstr(sec, "timezone"), tv.tv_sec);
+                print_time(json_gen, buffer, title, cfg_getstr(sec, "format"), cfg_getstr(sec, "timezone"), cfg_getstr(sec, "format_time"), tv.tv_sec);
                 SEC_CLOSE_MAP;
             }
 
diff --git a/include/i3status.h b/include/i3status.h
index 037e154..5f65c5e 100644
--- a/include/i3status.h
+++ b/include/i3status.h
@@ -8,6 +8,9 @@ enum { O_DZEN2,
        O_TERM,
        O_NONE } output_format;
 
+enum { M_PANGO,
+       M_NONE } markup_format;
+
 char *pct_mark;
 
 #include <stdbool.h>
@@ -78,6 +81,9 @@ char *pct_mark;
          * not forgotten in the module */                                                        \
         *outwalk = '\0';                                                                         \
         if (output_format == O_I3BAR) {                                                          \
+            char *_markup = cfg_getstr(cfg_general, "markup");                                   \
+            yajl_gen_string(json_gen, (const unsigned char *) "markup", strlen("markup"));       \
+            yajl_gen_string(json_gen, (const unsigned char *)_markup, strlen(_markup));          \
             yajl_gen_string(json_gen, (const unsigned char *) "full_text", strlen("full_text")); \
             yajl_gen_string(json_gen, (const unsigned char *)text, strlen(text));                \
         } else {                                                                                 \
@@ -176,6 +182,7 @@ void print_separator(const char *separator);
 char *color(const char *colorstr);
 char *endcolor() __attribute__((pure));
 void reset_cursor(void);
+void maybe_escape_markup(char *text, char **buffer);
 
 /* src/auto_detect_format.c */
 char *auto_detect_format();
@@ -193,7 +200,7 @@ const char *first_eth_interface(const net_type_t type);
 void print_ipv6_info(yajl_gen json_gen, char *buffer, const char *format_up, const char *format_down);
 void print_disk_info(yajl_gen json_gen, char *buffer, const char *path, const char *format, const char *format_not_mounted, const char *prefix_type, const char *threshold_type, const double low_threshold);
 void print_battery_info(yajl_gen json_gen, char *buffer, int number, const char *path, const char *format, const char *format_down, const char *status_chr, const char *status_bat, const char *status_full, int low_threshold, char *threshold_type, bool last_full_capacity, bool integer_battery_capacity, bool hide_seconds);
-void print_time(yajl_gen json_gen, char *buffer, const char *title, const char *format, const char *tz, time_t t);
+void print_time(yajl_gen json_gen, char *buffer, const char *title, const char *format, const char *tz, const char *format_time, time_t t);
 void print_ddate(yajl_gen json_gen, char *buffer, const char *format, time_t t);
 const char *get_ip_addr(const char *interface);
 void print_wireless_info(yajl_gen json_gen, char *buffer, const char *interface, const char *format_up, const char *format_down);
diff --git a/man/i3status.man b/man/i3status.man
index d550b81..0b5b367 100644
--- a/man/i3status.man
+++ b/man/i3status.man
@@ -184,6 +184,13 @@ format as the separator is drawn by i3bar directly otherwise. For the other
 output formats, the provided non-empty string will be automatically enclosed
 with the necessary coloring bits if color support is enabled.
 
+i3bar supports Pango markup, allowing your format strings to specify font
+color, size, etc. by setting the +markup+ directive to "pango". Note that the
+ampersand ("&"), less-than ("<"), greater-than (">"), single-quote ("'"), and
+double-quote (""") characters need to be replaced with "`&amp;`", "`&lt;`",
+"`&gt;`", "`&apos;`", and "`&quot;`" respectively. This is done automatically
+for generated content (e.g. wireless ESSID, time).
+
 *Example configuration*:
 -------------------------------------------------------------
 general {
@@ -417,6 +424,18 @@ in the +tztime+ module.
 
 *Example timezone*: +Europe/Berlin+
 
+If you would like to use markup in this section, there is a separate
++format_time+ option that is automatically escaped. Its output then replaces
+%time in the format string.
+
+*Example configuration (markup)*:
+-------------------------------------------------------------
+tztime time {
+	format = "<span foreground='#ffffff'>time:</span> %time"
+	format_time = "%H:%M %Z"
+}
+-------------------------------------------------------------
+
 === DDate
 
 Outputs the current discordian date in user-specified format. See +ddate(1)+ for
diff --git a/src/output.c b/src/output.c
index f7a8888..1c8c415 100644
--- a/src/output.c
+++ b/src/output.c
@@ -78,3 +78,46 @@ void print_separator(const char *separator) {
 void reset_cursor(void) {
     printf("\033[?25h");
 }
+
+/*
+ * Escapes ampersand, less-than, greater-than, single-quote, and double-quote
+ * characters with the corresponding Pango markup strings if markup is enabled.
+ * See the glib implementation:
+ * https://git.gnome.org/browse/glib/tree/glib/gmarkup.c?id=03db1f455b4265654e237d2ad55464b4113cba8a#n2142
+ *
+ */
+void maybe_escape_markup(char *text, char **buffer) {
+    if (markup_format == M_NONE) {
+        *buffer += sprintf(*buffer, "%s", text);
+        return;
+    }
+    for (; *text != '\0'; text++) {
+        switch (*text) {
+            case '&':
+                *buffer += sprintf(*buffer, "%s", "&amp;");
+                break;
+            case '<':
+                *buffer += sprintf(*buffer, "%s", "&lt;");
+                break;
+            case '>':
+                *buffer += sprintf(*buffer, "%s", "&gt;");
+                break;
+            case '\'':
+                *buffer += sprintf(*buffer, "%s", "&apos;");
+                break;
+            case '"':
+                *buffer += sprintf(*buffer, "%s", "&quot;");
+                break;
+            default:
+                if ((0x1 <= *text && *text <= 0x8) ||
+                    (0xb <= *text && *text <= 0xc) ||
+                    (0xe <= *text && *text <= 0x1f) ||
+                    (0x7f <= *text && *text <= 0x84) ||
+                    (0x86 <= *text && *text <= 0x9f))
+                    *buffer += sprintf(*buffer, "&#x%x;", *text);
+                else
+                    *(*buffer)++ = *text;
+                break;
+        }
+    }
+}
diff --git a/src/print_time.c b/src/print_time.c
index c70a09c..9fa6642 100644
--- a/src/print_time.c
+++ b/src/print_time.c
@@ -33,17 +33,36 @@ void set_timezone(const char *tz) {
     }
 }
 
-void print_time(yajl_gen json_gen, char *buffer, const char *title, const char *format, const char *tz, time_t t) {
+void print_time(yajl_gen json_gen, char *buffer, const char *title, const char *format, const char *tz, const char *format_time, time_t t) {
+    const char *walk;
     char *outwalk = buffer;
     struct tm tm;
+    char timebuf[1024];
 
     if (title != NULL)
         INSTANCE(title);
 
-    /* Convert time and format output. */
     set_timezone(tz);
     localtime_r(&t, &tm);
-    outwalk += strftime(outwalk, 4095, format, &tm);
+
+    if (format_time == NULL) {
+        strftime(timebuf, sizeof(timebuf), format, &tm);
+        maybe_escape_markup(timebuf, &outwalk);
+    } else {
+        for (walk = format; *walk != '\0'; walk++) {
+            if (*walk != '%') {
+                *(outwalk++) = *walk;
+                continue;
+            }
+
+            if (BEGINS_WITH(walk + 1, "time")) {
+                strftime(timebuf, sizeof(timebuf), format_time, &tm);
+                maybe_escape_markup(timebuf, &outwalk);
+                walk += strlen("time");
+            }
+        }
+    }
+
     *outwalk = '\0';
     OUTPUT_FULL_TEXT(buffer);
 }
diff --git a/src/print_wireless_info.c b/src/print_wireless_info.c
index aff0438..4f92507 100644
--- a/src/print_wireless_info.c
+++ b/src/print_wireless_info.c
@@ -519,7 +519,7 @@ void print_wireless_info(yajl_gen json_gen, char *buffer, const char *interface,
 
         if (BEGINS_WITH(walk + 1, "essid")) {
             if (info.flags & WIRELESS_INFO_FLAG_HAS_ESSID)
-                outwalk += sprintf(outwalk, "%s", info.essid);
+                maybe_escape_markup(info.essid, &outwalk);
             else
                 *(outwalk++) = '?';
             walk += strlen("essid");
-- 
cgit v1.2.3