]> git.sur5r.net Git - i3/i3/commitdiff
Implement the i3bar JSON protocol (with fallback to plain text)
authorMichael Stapelberg <michael@stapelberg.de>
Thu, 16 Feb 2012 23:27:11 +0000 (23:27 +0000)
committerMichael Stapelberg <michael@stapelberg.de>
Thu, 16 Feb 2012 23:28:18 +0000 (23:28 +0000)
If the first line of the input starts with {"version":, then the input is
considered to be JSON, otherwise it is interpreted as plain text.

Only the "full_text" and "color" parts of a block are currently understood by
i3bar.

i3bar/include/common.h
i3bar/src/child.c
i3bar/src/xcb.c

index bce31a4df22cd5c267d3bf9626b97025d424f2cb..212b9dd1d8d55d887de02c2aa6f693a21b4d6b3e 100644 (file)
@@ -9,6 +9,9 @@
 #define COMMON_H_
 
 #include <stdbool.h>
+#include <xcb/xcb.h>
+#include <xcb/xproto.h>
+#include "queue.h"
 
 typedef struct rect_t rect;
 
@@ -23,7 +26,27 @@ struct rect_t {
     int h;
 };
 
-#include "queue.h"
+/* This data structure represents one JSON dictionary, multiple of these make
+ * up one status line. */
+struct status_block {
+    char *full_text;
+
+    char *color;
+
+    /* full_text, but converted to UCS-2. This variable is only temporarily
+     * used in refresh_statusline(). */
+    xcb_char2b_t *ucs2_full_text;
+    size_t glyph_count_full_text;
+
+    /* The amount of pixels necessary to render this block. This variable is
+     * only temporarily used in refresh_statusline(). */
+    uint32_t width;
+
+    TAILQ_ENTRY(status_block) blocks;
+};
+
+TAILQ_HEAD(statusline_head, status_block) statusline_head;
+
 #include "child.h"
 #include "ipc.h"
 #include "outputs.h"
index e294fb9abe6886424a3efb029ed8a7226e4575f9..41bb8832b02972d258c668ffaaa35fde5a92e4e6 100644 (file)
@@ -18,6 +18,8 @@
 #include <errno.h>
 #include <err.h>
 #include <ev.h>
+#include <yajl/yajl_common.h>
+#include <yajl/yajl_parse.h>
 
 #include "common.h"
 
@@ -28,7 +30,25 @@ pid_t child_pid;
 ev_io    *stdin_io;
 ev_child *child_sig;
 
+/* JSON parser for stdin */
+bool first_line = true;
+bool plaintext = false;
+yajl_callbacks callbacks;
+yajl_handle parser;
+
+typedef struct parser_ctx {
+    /* A copy of the last JSON map key. */
+    char *last_map_key;
+
+    /* The current block. Will be filled, then copied and put into the list of
+     * blocks. */
+    struct status_block block;
+} parser_ctx;
+
+parser_ctx parser_context;
+
 /* The buffer statusline points to */
+struct statusline_head statusline_head = TAILQ_HEAD_INITIALIZER(statusline_head);
 char *statusline_buffer = NULL;
 
 /*
@@ -50,6 +70,70 @@ void cleanup() {
     }
 }
 
+/*
+ * The start of a new array is the start of a new status line, so we clear all
+ * previous entries.
+ *
+ */
+static int stdin_start_array(void *context) {
+    struct status_block *first;
+    while (!TAILQ_EMPTY(&statusline_head)) {
+        first = TAILQ_FIRST(&statusline_head);
+        FREE(first->full_text);
+        FREE(first->color);
+        TAILQ_REMOVE(&statusline_head, first, blocks);
+        free(first);
+    }
+    return 1;
+}
+
+/*
+ * The start of a map is the start of a single block of the status line.
+ *
+ */
+static int stdin_start_map(void *context) {
+    parser_ctx *ctx = context;
+    memset(&(ctx->block), '\0', sizeof(struct status_block));
+    return 1;
+}
+
+static int stdin_map_key(void *context, const unsigned char *key, size_t len) {
+    parser_ctx *ctx = context;
+    FREE(ctx->last_map_key);
+    sasprintf(&(ctx->last_map_key), "%.*s", len, key);
+    return 1;
+}
+
+static int stdin_string(void *context, const unsigned char *val, size_t len) {
+    parser_ctx *ctx = context;
+    if (strcasecmp(ctx->last_map_key, "full_text") == 0) {
+        sasprintf(&(ctx->block.full_text), "%.*s", len, val);
+    }
+    if (strcasecmp(ctx->last_map_key, "color") == 0) {
+        sasprintf(&(ctx->block.color), "%.*s", len, val);
+    }
+    return 1;
+}
+
+static int stdin_end_map(void *context) {
+    parser_ctx *ctx = context;
+    struct status_block *new_block = smalloc(sizeof(struct status_block));
+    memcpy(new_block, &(ctx->block), sizeof(struct status_block));
+    TAILQ_INSERT_TAIL(&statusline_head, new_block, blocks);
+    return 1;
+}
+
+static int stdin_end_array(void *context) {
+    DLOG("dumping statusline:\n");
+    struct status_block *current;
+    TAILQ_FOREACH(current, &statusline_head, blocks) {
+        DLOG("full_text = %s\n", current->full_text);
+        DLOG("color = %s\n", current->color);
+    }
+    DLOG("end of dump\n");
+    return 1;
+}
+
 /*
  * Callbalk for stdin. We read a line from stdin and store the result
  * in statusline
@@ -60,25 +144,19 @@ void stdin_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
     int n = 0;
     int rec = 0;
     int buffer_len = STDIN_CHUNK_SIZE;
-    char *buffer = smalloc(buffer_len);
+    unsigned char *buffer = smalloc(buffer_len);
     buffer[0] = '\0';
     while(1) {
         n = read(fd, buffer + rec, buffer_len - rec);
         if (n == -1) {
             if (errno == EAGAIN) {
-                /* remove trailing newline and finish up */
-                buffer[rec-1] = '\0';
+                /* finish up */
                 break;
             }
             ELOG("read() failed!: %s\n", strerror(errno));
             exit(EXIT_FAILURE);
         }
         if (n == 0) {
-            if (rec != 0) {
-                /* remove trailing newline and finish up */
-                buffer[rec-1] = '\0';
-            }
-
             /* end of file, kill the watcher */
             ELOG("stdin: received EOF\n");
             cleanup();
@@ -96,13 +174,41 @@ void stdin_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
         FREE(buffer);
         return;
     }
-    FREE(statusline_buffer);
-    statusline = statusline_buffer = buffer;
-    for (n = 0; buffer[n] != '\0'; ++n) {
-        if (buffer[n] == '\n')
-            statusline = &buffer[n + 1];
+
+    unsigned char *json_input = buffer;
+    if (first_line) {
+        DLOG("Detecting input type based on buffer *%.*s*\n", rec, buffer);
+        /* Detect whether this is JSON or plain text. */
+        plaintext = (strncasecmp((char*)buffer, "{\"version\":", strlen("{\"version\":")) != 0);
+        if (plaintext) {
+            /* In case of plaintext, we just add a single block and change its
+             * full_text pointer later. */
+            struct status_block *new_block = scalloc(sizeof(struct status_block));
+            TAILQ_INSERT_TAIL(&statusline_head, new_block, blocks);
+        } else {
+            /* At the moment, we don’t care for the version. This might change
+             * in the future, but for now, we just discard it. */
+            while (*json_input != '\n' && *json_input != '\0') {
+                json_input++;
+                rec--;
+            }
+        }
+        first_line = false;
+    }
+    if (!plaintext) {
+        yajl_status status = yajl_parse(parser, json_input, rec);
+        if (status != yajl_status_ok) {
+            fprintf(stderr, "[i3bar] Could not parse JSON input: %.*s\n",
+                    rec, json_input);
+        }
+        free(buffer);
+    } else {
+        struct status_block *first = TAILQ_FIRST(&statusline_head);
+        /* Remove the trailing newline and terminate the string at the same
+         * time. */
+        buffer[rec-1] = '\0';
+        first->full_text = (char*)buffer;
     }
-    DLOG("%s\n", statusline);
     draw_bars();
 }
 
@@ -126,6 +232,16 @@ void child_sig_cb(struct ev_loop *loop, ev_child *watcher, int revents) {
  *
  */
 void start_child(char *command) {
+    /* Allocate a yajl parser which will be used to parse stdin. */
+    memset(&callbacks, '\0', sizeof(yajl_callbacks));
+    callbacks.yajl_map_key = stdin_map_key;
+    callbacks.yajl_string = stdin_string;
+    callbacks.yajl_start_array = stdin_start_array;
+    callbacks.yajl_end_array = stdin_end_array;
+    callbacks.yajl_start_map = stdin_start_map;
+    callbacks.yajl_end_map = stdin_end_map;
+    parser = yajl_alloc(&callbacks, NULL, &parser_context);
+
     child_pid = 0;
     if (command != NULL) {
         int fd[2];
index f75f23deb4795aab4c4c327d2fd4bd8cc3252950..a3312ca3120853a24291c3b13f890b1f47cfa858 100644 (file)
@@ -108,28 +108,51 @@ int _xcb_request_failed(xcb_void_cookie_t cookie, char *err_msg, int line) {
  *
  */
 void refresh_statusline() {
-    size_t glyph_count;
+    struct status_block *block;
 
-    if (statusline == NULL) {
-        return;
+    uint32_t old_statusline_width = statusline_width;
+    statusline_width = 0;
+
+    /* Convert all blocks from UTF-8 to UCS-2 and predict the text width (in
+     * pixels). */
+    TAILQ_FOREACH(block, &statusline_head, blocks) {
+        block->ucs2_full_text = (xcb_char2b_t*)convert_utf8_to_ucs2(block->full_text, &(block->glyph_count_full_text));
+        block->width = predict_text_width((char*)block->ucs2_full_text, block->glyph_count_full_text, true);
+        /* If this is not the last block, add some pixels for a separator. */
+        if (TAILQ_NEXT(block, blocks) != NULL)
+            block->width += 9;
+        statusline_width += block->width;
     }
 
-    xcb_char2b_t *text = (xcb_char2b_t*)convert_utf8_to_ucs2(statusline, &glyph_count);
-    uint32_t old_statusline_width = statusline_width;
-    statusline_width = predict_text_width((char*)text, glyph_count, true);
     /* If the statusline is bigger than our screen we need to make sure that
      * the pixmap provides enough space, so re-allocate if the width grew */
     if (statusline_width > xcb_screen->width_in_pixels &&
         statusline_width > old_statusline_width)
         realloc_sl_buffer();
 
+    /* Clear the statusline pixmap. */
     xcb_rectangle_t rect = { 0, 0, xcb_screen->width_in_pixels, font.height };
     xcb_poly_fill_rectangle(xcb_connection, statusline_pm, statusline_clear, 1, &rect);
-    set_font_colors(statusline_ctx, colors.bar_fg, colors.bar_bg);
-    draw_text((char*)text, glyph_count, true, statusline_pm, statusline_ctx,
-            0, 0, xcb_screen->width_in_pixels);
 
-    FREE(text);
+    /* Draw the text of each block. */
+    uint32_t x = 0;
+    TAILQ_FOREACH(block, &statusline_head, blocks) {
+        uint32_t colorpixel = (block->color ? get_colorpixel(block->color) : colors.bar_fg);
+        set_font_colors(statusline_ctx, colorpixel, colors.bar_bg);
+        draw_text((char*)block->ucs2_full_text, block->glyph_count_full_text,
+                  true, statusline_pm, statusline_ctx, x, 0, block->width);
+        x += block->width;
+
+        if (TAILQ_NEXT(block, blocks) != NULL) {
+            /* This is not the last block, draw a separator. */
+            set_font_colors(statusline_ctx, get_colorpixel("#666666"), colors.bar_bg);
+            xcb_poly_line(xcb_connection, XCB_COORD_MODE_ORIGIN, statusline_pm,
+                          statusline_ctx, 2,
+                          (xcb_point_t[]){ { x - 5, 2 }, { x - 5, font.height - 2 } });
+        }
+
+        FREE(block->ucs2_full_text);
+    }
 }
 
 /*
@@ -1371,7 +1394,7 @@ void draw_bars() {
                                 1,
                                 &rect);
 
-        if (statusline != NULL) {
+        if (!TAILQ_EMPTY(&statusline_head)) {
             DLOG("Printing statusline!\n");
 
             /* Luckily we already prepared a seperate pixmap containing the rendered