From 9dccd2ed48aff688e19cbce5f2b548b0b47ee526 Mon Sep 17 00:00:00 2001 From: Artem Shinkarov Date: Tue, 28 Apr 2015 14:25:01 +0100 Subject: [PATCH 1/2] Supporting *.xbm icons in i3bar. Very often in order to display non-default information in the status bar it comes handy to use icons near the text for aesthetic reasons and for faster navigation. XBM bitmaps is a very simple format to store pixel maps, and this patch allows to specify in the i3bar block two additional fields: icon, and icon_color. Icon field contains a path to the *.xbm file, and the icon_color -- colour to use when drawing the icon. XBM file parsing is written by hands, as Xlib implementation is not that easy to integrate in the xcb environment. People who contributed: * Artem Shinkarov Original patch. * woho (https://github.com/woho) Fixed icon coloring problem on Arch Linux. * soulofmachines (https://github.com/soulofmachines) Fixed click event, making sure it considers the icon if present. * btolsch (https://github.com/btolsch) Fixed an error when the xcb_image was created height and width were specified in the wrong order: https://github.com/ashinkarov/i3-extras/pull/29 --- common.mk | 2 + i3bar/include/common.h | 4 + i3bar/include/xbm_image.h | 14 +++ i3bar/src/child.c | 11 ++ i3bar/src/xbm_image.c | 278 ++++++++++++++++++++++++++++++++++++++++++++++ i3bar/src/xcb.c | 46 +++++++- 6 files changed, 353 insertions(+), 2 deletions(-) create mode 100644 i3bar/include/xbm_image.h create mode 100644 i3bar/src/xbm_image.c diff --git a/common.mk b/common.mk index 4fe8f2b..bef7877 100644 --- a/common.mk +++ b/common.mk @@ -89,8 +89,10 @@ ldflags_for_lib = $(shell $(PKG_CONFIG) --exists 2>/dev/null $(1) && $(PKG_CONFI # XCB common stuff XCB_CFLAGS := $(call cflags_for_lib, xcb) XCB_CFLAGS += $(call cflags_for_lib, xcb-event) +XCB_CFLAGS += $(call cflags_for_lib, xcb-image) XCB_LIBS := $(call ldflags_for_lib, xcb,xcb) XCB_LIBS += $(call ldflags_for_lib, xcb-event,xcb-event) +XCB_LIBS += $(call ldflags_for_lib, xcb-image,xcb-image) ifeq ($(shell $(PKG_CONFIG) --exists xcb-util 2>/dev/null || echo 1),1) XCB_CFLAGS += $(call cflags_for_lib, xcb-atom) XCB_CFLAGS += $(call cflags_for_lib, xcb-aux) diff --git a/i3bar/include/common.h b/i3bar/include/common.h index 0d46ab6..9e47dae 100644 --- a/i3bar/include/common.h +++ b/i3bar/include/common.h @@ -12,6 +12,7 @@ #include #include "libi3.h" #include "queue.h" +#include "xbm_image.h" typedef struct rect_t rect; @@ -57,6 +58,9 @@ struct status_block { blockalign_t align; + struct xbm_image *icon; + char *icon_color; + bool urgent; bool no_separator; bool pango_markup; diff --git a/i3bar/include/xbm_image.h b/i3bar/include/xbm_image.h new file mode 100644 index 0000000..3c01c84 --- /dev/null +++ b/i3bar/include/xbm_image.h @@ -0,0 +1,14 @@ +#ifndef __XBM_IMAGE_H__ +#define __XBM_IMAGE_H__ + +struct xbm_image { + int width, height; + char * id; + char * data; +}; + + +struct xbm_image * xbm_from_file (char *); +void xbm_free (struct xbm_image *); + +#endif /* __XBM_IMAGE_H__ */ diff --git a/i3bar/src/child.c b/i3bar/src/child.c index 3570dde..991e920 100644 --- a/i3bar/src/child.c +++ b/i3bar/src/child.c @@ -77,6 +77,8 @@ static void clear_statusline(struct statusline_head *head, bool free_resources) FREE(first->min_width_str); FREE(first->background); FREE(first->border); + FREE(first->icon_color); + xbm_free (first->icon); } TAILQ_REMOVE(head, first, blocks); @@ -219,6 +221,15 @@ static int stdin_string(void *context, const unsigned char *val, size_t len) { ctx->block.pango_markup = (len == strlen("pango") && !strncasecmp((const char *)val, "pango", strlen("pango"))); return 1; } + if (strcasecmp(ctx->last_map_key, "icon") == 0) { + char * s; + sasprintf(&s, "%.*s", len, val); + ctx->block.icon = xbm_from_file(s); + FREE(s); + } + if (strcasecmp(ctx->last_map_key, "icon_color") == 0) { + sasprintf(&(ctx->block.icon_color), "%.*s", len, val); + } if (strcasecmp(ctx->last_map_key, "align") == 0) { if (len == strlen("center") && !strncmp((const char *)val, "center", strlen("center"))) { ctx->block.align = ALIGN_CENTER; diff --git a/i3bar/src/xbm_image.c b/i3bar/src/xbm_image.c new file mode 100644 index 0000000..ea99ffa --- /dev/null +++ b/i3bar/src/xbm_image.c @@ -0,0 +1,278 @@ +#include +#include +#include +#include +#include + +#include "xbm_image.h" + +#if FILENAME_CHECKING +/* Assume that file is called xxx.xbm, get xxx out of the filename + for furhter validation during parsing. */ +static char * +validate_fname (char * fname) +{ + char * dot = strrchr (fname, '.'); + char * slash = strrchr (fname, '/'); + char * ret = NULL; + unsigned sz = 0; + + if (NULL == dot) + return NULL; + + if (0 != strncmp (dot, ".xbm", 4)) + return NULL; + + if (slash) + fname = slash + 1; + + sz = dot - fname; + ret = malloc (sz + 1); + strncpy (ret, fname, sz); + ret[sz] = 0; + return ret; +} +#endif + +static char * +read_id (FILE * f) +{ + unsigned sz = 2, ptr = 0; + char * data = malloc (sz); + int c; + + c = fgetc (f); + if (isalpha (c) || c == '_') + data[ptr++] = c; + else + goto out; + + while (true) { + c = fgetc (f); + if (ptr == sz - 1) + data = realloc (data, sz *= 2); + + if (isalnum (c) || c == '_') + data[ptr++] = c; + else { + ungetc (c, f); + break; + } + } + + data[ptr] = '\0'; + return data; + +out: + if (data) + free (data); + + return NULL; +} + + +static bool +read_string (FILE * f, const char * s) +{ + while (*s != '\0') { + int c = fgetc (f); + if (c == EOF || c != *s) { + ungetc (c, f); + return false; + } + s++; + } + + return true; +} + +static bool +skip_spaces (FILE * f) +{ + int c; + while (isspace (c = fgetc (f)) && c != EOF) + ; + + ungetc (c, f); + return true; +} + +static inline bool +string_ends_with (const char * s, const char * postfix) +{ + return strlen (s) > strlen (postfix) + && strncmp (s + strlen (s) - strlen (postfix), + postfix, strlen (postfix)) == 0; +} + +#define READ_WORD_EAT_SPACES(file, word) \ +do { \ + if (!read_string (file, word)) \ + return false; \ + skip_spaces (file); \ +} while (0) + + +static bool +read_define (FILE * f, struct xbm_image * img) +{ + unsigned sz; + char * wh; + + READ_WORD_EAT_SPACES (f, "define"); + + /* read the _width variable, and save for + later validation in the img. */ + if (NULL == (wh = read_id (f))) + return false; + + if (string_ends_with (wh, "_width")) { + if (!img->id) { + unsigned idsz = strlen (wh) - strlen ("_width"); + img->id = malloc (idsz+1); + strncpy (img->id, wh, idsz); + img->id[idsz] = '\0'; + } else if (0 != strncmp (img->id, wh, strlen (img->id))) { + free (wh); + return false; + } + + free (wh); + skip_spaces (f); + if (fscanf (f, "%u", &sz) < 1) + return false; + + img->width = sz; + } else if (string_ends_with (wh, "_height")) { + if (!img->id) { + unsigned idsz = strlen (wh) - strlen ("_height"); + img->id = malloc (idsz+1); + strncpy (img->id, wh, idsz); + img->id[idsz] = '\0'; + } else if (0 != strncmp (img->id, wh, strlen (img->id))) { + free (wh); + return false; + } + + free (wh); + skip_spaces (f); + if (fscanf (f, "%u", &sz) < 1) + return false; + + img->height = sz; + } else + return false; + + return true; +} + +static bool +read_data (FILE * f, struct xbm_image * img) +{ + int sz, i; + char * data; + + /* size in bytes */ + sz = (img->width / 8 + !!(img->width % 8)) * img->height; + + data = malloc (sz); + + READ_WORD_EAT_SPACES (f, "tatic"); + READ_WORD_EAT_SPACES (f, "unsigned"); + READ_WORD_EAT_SPACES (f, "char"); + READ_WORD_EAT_SPACES (f, img->id); + READ_WORD_EAT_SPACES (f, "_bits[]"); + READ_WORD_EAT_SPACES (f, "="); + READ_WORD_EAT_SPACES (f, "{"); + + for (i = 0; i < sz; i++) { + unsigned value; + if (fscanf (f, "%x", &value) < 1 || value > 255) + return false; + + data[i] = (char)value; + skip_spaces (f); + if (i != sz - 1) + READ_WORD_EAT_SPACES (f, ","); + } + + READ_WORD_EAT_SPACES (f, "}"); + READ_WORD_EAT_SPACES (f, ";"); + img->data = data; + return true; +} + + +struct xbm_image * +xbm_from_file (char * fname) +{ + struct xbm_image * img = NULL; + FILE * f = NULL; + int c; + + if (!(f = fopen (fname, "r"))) + goto out; + + img = malloc (sizeof (struct xbm_image)); + img->id = NULL; + img->width = -1; + img->height = -1; + + do { + c = fgetc (f); + if (c == '#') { + if (!read_define (f, img)) + goto out; + } else if (c == 's') { + if (img->width == -1 || img->height == -1 || !img->id + || !read_data (f, img)) + goto out; + } else if (isspace (c) || c == EOF) + ; + else + goto out; + } while (c != EOF); + + fclose (f); + return img; + +out: + if (f) + fclose (f); + + return NULL; +} + +void +xbm_free (struct xbm_image * img) +{ + if (!img) + return; + + if (img->id) + free (img->id); + if (img->data) + free (img->data); + + free (img); +} + +#ifdef TESTING +int +main (int argc, char *argv[]) +{ + struct xbm_image * img; + int i; + + img = xbm_from_file (argv[1]); + + if (img != NULL) { + for (i = 0; i < (img->width /8 + !!(img->width % 8)) * img->height; i++) + printf ("%x, ", (unsigned char)img->data[i]); + + xbm_free (img); + } + + return EXIT_SUCCESS; +} +#endif diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index 496035c..c9a4573 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -12,6 +12,7 @@ #include #include #include +#include #ifdef XCB_COMPAT #include "xcb_compat.h" @@ -209,7 +210,7 @@ uint32_t predict_statusline_length(bool use_short_text) { render = &block->short_render; } - if (i3string_get_num_bytes(text) == 0) + if (i3string_get_num_bytes(text) == 0 && block->icon == NULL) continue; render->width = predict_text_width(text); @@ -241,6 +242,10 @@ uint32_t predict_statusline_length(bool use_short_text) { /* If this is not the last block, add some pixels for a separator. */ if (TAILQ_NEXT(block, blocks) != NULL) width += block->sep_block_width; + + /* Add some space between the text and the icon. */ + if (block->icon) + width += block->icon->width + 5; } return width; @@ -272,9 +277,42 @@ void draw_statusline(i3_output *output, uint32_t clip_left, bool use_focus_color render = &block->short_render; } - if (i3string_get_num_bytes(text) == 0) + if (i3string_get_num_bytes(block->full_text) == 0 && block->icon == NULL) continue; + if (block->icon != NULL) { + xcb_image_t * img; + + img = xcb_image_create_native (conn, block->icon->width, + block->icon->height, + XCB_IMAGE_FORMAT_XY_BITMAP, + 1, NULL, ~0, NULL); + + img->data = malloc (img->size); + memset (img->data, 0, img->size); + + for (int i = 0; i < block->icon->height; i++) + for (int j = 0; j < block->icon->width; j++) { + unsigned pos = (img->width /8 + !!(img->width % 8))*i + j/8; + bool p = !!(block->icon->data[pos] & (1 << (j%8))); + xcb_image_put_pixel (img, j, i, p); + } + + uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND; + if (block->icon_color) { + uint32_t values[] = { get_colorpixel(block->icon_color), colors.bar_bg.colorpixel }; + xcb_change_gc(xcb_connection, output->statusline_buffer.gc, mask, values); + } else { + uint32_t values[] = { colors.bar_fg.colorpixel, colors.bar_bg.colorpixel }; + xcb_change_gc(xcb_connection, output->statusline_buffer.gc, mask, values); + } + + xcb_image_put (conn, output->statusline_buffer.id, output->statusline_buffer.gc, + img, x, 3 + (font.height - block->icon->height)/2, 0); + xcb_image_destroy (img); + x += block->icon->width + 5; + } + color_t fg_color; if (block->urgent) { fg_color = colors.urgent_ws_fg; @@ -508,6 +546,10 @@ void handle_button(xcb_button_press_event_t *event) { last_block_x = block_x; block_x += render->width + render->x_offset + render->x_append + get_sep_offset(block) + sep_offset_remainder; + /* Add icon width */ + if (block->icon) + block_x += block->icon->width + 5; + if (statusline_x <= block_x && statusline_x >= last_block_x) { send_block_clicked(event->detail, block->name, block->instance, event->root_x, event->root_y); return; -- 2.7.4