summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Stapelberg <stapelberg@users.noreply.github.com>2021-06-13 08:35:52 +0200
committerGitHub <noreply@github.com>2021-06-13 08:35:52 +0200
commitabbf6a85d712ef1790b02122eb432e4bafe20576 (patch)
tree314e1f7ea8fed5665a71b84b94d21cdcf7c4b66f
parenteaa5e636f96815ddf7f236d35aa9b8686388aa76 (diff)
downloadi3-abbf6a85d712ef1790b02122eb432e4bafe20576.tar.gz
i3-abbf6a85d712ef1790b02122eb432e4bafe20576.zip
Implement showing window icons in titlebar (#4439)
This feature defaults to off, and can be turned on for individual windows, or (with for_window) for all new windows. See the userguide change. This commit is partially based on work by: • Marius Muja • mickael9 • Esteve Varela Colominas • Bernardo Menicagli
-rw-r--r--RELEASE-NOTES-next1
-rw-r--r--docs/userguide26
-rw-r--r--include/commands.h6
-rw-r--r--include/data.h9
-rw-r--r--include/i3-atoms_rest.xmacro.h1
-rw-r--r--include/libi3.h11
-rw-r--r--include/window.h6
-rw-r--r--libi3/boolstr.c25
-rw-r--r--libi3/draw_util.c24
-rw-r--r--meson.build1
-rw-r--r--parser-specs/commands.spec15
-rw-r--r--src/commands.c29
-rw-r--r--src/con.c1
-rw-r--r--src/config_directives.c35
-rw-r--r--src/handlers.c12
-rw-r--r--src/ipc.c3
-rw-r--r--src/load_layout.c4
-rw-r--r--src/manage.c4
-rw-r--r--src/window.c110
-rw-r--r--src/x.c32
-rw-r--r--testcases/t/116-nestedcons.t1
-rw-r--r--testcases/t/187-commands-parser.t1
-rw-r--r--testcases/t/314-window-icon-padding.t62
23 files changed, 392 insertions, 27 deletions
diff --git a/RELEASE-NOTES-next b/RELEASE-NOTES-next
index e6a892d6..3b6de271 100644
--- a/RELEASE-NOTES-next
+++ b/RELEASE-NOTES-next
@@ -36,6 +36,7 @@ option is enabled and only then sets a screenshot as background.
• i3-dump-log -f now uses UNIX sockets instead of pthreads. The UNIX socket approach
should be more reliable and also more portable.
• Implement the include config directive
+ • Implement optionally showing window icons in titlebar
• Allow for_window to match against WM_CLIENT_MACHINE
• Add %machine placeholder (WM_CLIENT_MACHINE) to title_format
• Allow multiple output names in 'move container|workspace to output'
diff --git a/docs/userguide b/docs/userguide
index 944f7b39..05eb4aac 100644
--- a/docs/userguide
+++ b/docs/userguide
@@ -2703,6 +2703,32 @@ for_window [class=".*"] title_format "<b>%title</b>"
for_window [class="(?i)firefox"] title_format "<span foreground='red'>%title</span>"
-------------------------------------------------------------------------------------
+[[title_window_icon]]
+=== Window title icon
+
+By default, i3 does not display the window icon in the title bar.
+
+Starting with i3 v4.20, you can optionally enable window icons either for
+specific windows or for all windows (using the <<for_window>> directive).
+
+*Syntax*:
+-----------------------------
+title_window_icon <yes|no>
+title_window_icon padding <px>
+------------------------------
+
+*Examples*:
+-------------------------------------------------------------------------------------
+# show the window icon for the focused window to make it stand out
+bindsym $mod+p title_window_icon on
+
+# enable window icons for all windows
+for_window [class=".*"] title_window_icon on
+
+# enable window icons for all windows with extra horizontal padding
+for_window [class=".*"] title_window_icon padding 3px
+-------------------------------------------------------------------------------------
+
=== Changing border style
To change the border of the current client, you can use +border normal+ to use the normal
diff --git a/include/commands.h b/include/commands.h
index 47ac5dbf..c27cba4a 100644
--- a/include/commands.h
+++ b/include/commands.h
@@ -331,3 +331,9 @@ void cmd_shmlog(I3_CMD, const char *argument);
*
*/
void cmd_debuglog(I3_CMD, const char *argument);
+
+/**
+ * Implementation of 'title_window_icon <yes|no>' and 'title_window_icon padding <px>'
+ *
+ */
+void cmd_title_window_icon(I3_CMD, const char *enable, int padding);
diff --git a/include/data.h b/include/data.h
index 1d47af64..0679adf7 100644
--- a/include/data.h
+++ b/include/data.h
@@ -15,6 +15,7 @@
#include <xcb/randr.h>
#include <pcre.h>
#include <sys/time.h>
+#include <cairo/cairo.h>
#include "queue.h"
@@ -471,6 +472,9 @@ struct Window {
double min_aspect_ratio;
double max_aspect_ratio;
+ /** Window icon, as Cairo surface */
+ cairo_surface_t *icon;
+
/** The window has a nonrectangular shape. */
bool shaped;
/** The window has a nonrectangular input shape. */
@@ -656,6 +660,11 @@ struct Con {
/** The format with which the window's name should be displayed. */
char *title_format;
+ /** Whether the window icon should be displayed, and with what padding. -1
+ * means display no window icon (default behavior), 0 means display without
+ * any padding, 1 means display with 1 pixel of padding and so on. */
+ int window_icon_padding;
+
/* a sticky-group is an identifier which bundles several containers to a
* group. The contents are shared between all of them, that is they are
* displayed on whichever of the containers is currently visible */
diff --git a/include/i3-atoms_rest.xmacro.h b/include/i3-atoms_rest.xmacro.h
index 288a4211..ef50d81b 100644
--- a/include/i3-atoms_rest.xmacro.h
+++ b/include/i3-atoms_rest.xmacro.h
@@ -3,6 +3,7 @@
xmacro(_NET_WM_USER_TIME) \
xmacro(_NET_STARTUP_ID) \
xmacro(_NET_WORKAREA) \
+xmacro(_NET_WM_ICON) \
xmacro(WM_PROTOCOLS) \
xmacro(WM_DELETE_WINDOW) \
xmacro(UTF8_STRING) \
diff --git a/include/libi3.h b/include/libi3.h
index a93b3ff9..d98fa1fe 100644
--- a/include/libi3.h
+++ b/include/libi3.h
@@ -612,6 +612,11 @@ color_t draw_util_hex_to_color(const char *color);
void draw_util_text(i3String *text, surface_t *surface, color_t fg_color, color_t bg_color, int x, int y, int max_width);
/**
+ * Draw the given image using libi3.
+ */
+void draw_util_image(cairo_surface_t *image, surface_t *surface, int x, int y, int width, int height);
+
+/**
* Draws a filled rectangle.
* This function is a convenience wrapper and takes care of flushing the
* surface as well as restoring the cairo state.
@@ -668,3 +673,9 @@ void set_screenshot_as_wallpaper(xcb_connection_t *conn, xcb_screen_t *screen);
* content of the window.
*/
bool is_background_set(xcb_connection_t *conn, xcb_screen_t *screen);
+
+/**
+ * Reports whether str represents the enabled state (1, yes, true, …).
+ *
+ */
+bool boolstr(const char *str);
diff --git a/include/window.h b/include/window.h
index 858bf0cd..9759cc4b 100644
--- a/include/window.h
+++ b/include/window.h
@@ -101,3 +101,9 @@ void window_update_motif_hints(i3Window *win, xcb_get_property_reply_t *prop, bo
*
*/
void window_update_machine(i3Window *win, xcb_get_property_reply_t *prop);
+
+/**
+ * Updates the _NET_WM_ICON
+ *
+ */
+void window_update_icon(i3Window *win, xcb_get_property_reply_t *prop);
diff --git a/libi3/boolstr.c b/libi3/boolstr.c
new file mode 100644
index 00000000..0fa417dd
--- /dev/null
+++ b/libi3/boolstr.c
@@ -0,0 +1,25 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ */
+#include "libi3.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+/*
+ * Reports whether str represents the enabled state (1, yes, true, …).
+ *
+ */
+bool boolstr(const char *str) {
+ return (strcasecmp(str, "1") == 0 ||
+ strcasecmp(str, "yes") == 0 ||
+ strcasecmp(str, "true") == 0 ||
+ strcasecmp(str, "on") == 0 ||
+ strcasecmp(str, "enable") == 0 ||
+ strcasecmp(str, "active") == 0);
+}
diff --git a/libi3/draw_util.c b/libi3/draw_util.c
index ad2e6aa2..dd0900ce 100644
--- a/libi3/draw_util.c
+++ b/libi3/draw_util.c
@@ -141,6 +141,30 @@ void draw_util_text(i3String *text, surface_t *surface, color_t fg_color, color_
cairo_surface_mark_dirty(surface->surface);
}
+/**
+ * Draw the given image using libi3.
+ * This function is a convenience wrapper and takes care of flushing the
+ * surface as well as restoring the cairo state.
+ *
+ */
+void draw_util_image(cairo_surface_t *image, surface_t *surface, int x, int y, int width, int height) {
+ RETURN_UNLESS_SURFACE_INITIALIZED(surface);
+
+ cairo_save(surface->cr);
+
+ cairo_translate(surface->cr, x, y);
+
+ const int src_width = cairo_image_surface_get_width(image);
+ const int src_height = cairo_image_surface_get_height(image);
+ double scale = MIN((double)width / src_width, (double)height / src_height);
+ cairo_scale(surface->cr, scale, scale);
+
+ cairo_set_source_surface(surface->cr, image, 0, 0);
+ cairo_paint(surface->cr);
+
+ cairo_restore(surface->cr);
+}
+
/*
* Draws a filled rectangle.
* This function is a convenience wrapper and takes care of flushing the
diff --git a/meson.build b/meson.build
index d86e9dcd..00cd9e94 100644
--- a/meson.build
+++ b/meson.build
@@ -325,6 +325,7 @@ ev_dep = cc.find_library('ev')
inc = include_directories('include')
libi3srcs = [
+ 'libi3/boolstr.c',
'libi3/create_socket.c',
'libi3/dpi.c',
'libi3/draw_util.c',
diff --git a/parser-specs/commands.spec b/parser-specs/commands.spec
index 4ec3e22f..43c784a2 100644
--- a/parser-specs/commands.spec
+++ b/parser-specs/commands.spec
@@ -40,6 +40,7 @@ state INITIAL:
'scratchpad' -> SCRATCHPAD
'swap' -> SWAP
'title_format' -> TITLE_FORMAT
+ 'title_window_icon' -> TITLE_WINDOW_ICON
'mode' -> MODE
'bar' -> BAR
@@ -462,6 +463,20 @@ state TITLE_FORMAT:
format = string
-> call cmd_title_format($format)
+state TITLE_WINDOW_ICON:
+ 'padding'
+ -> TITLE_WINDOW_ICON_PADDING
+ enable = '1', 'yes', 'true', 'on', 'enable', 'active', '0', 'no', 'false', 'off', 'disable', 'inactive'
+ -> call cmd_title_window_icon($enable, 0)
+
+state TITLE_WINDOW_ICON_PADDING:
+ end
+ -> call cmd_title_window_icon($enable, &padding)
+ 'px'
+ -> call cmd_title_window_icon($enable, &padding)
+ padding = number
+ ->
+
# bar (hidden_state hide|show|toggle)|(mode dock|hide|invisible|toggle) [<bar_id>]
state BAR:
'hidden_state'
diff --git a/src/commands.c b/src/commands.c
index d256299d..798e2f6c 100644
--- a/src/commands.c
+++ b/src/commands.c
@@ -2038,6 +2038,35 @@ void cmd_title_format(I3_CMD, const char *format) {
}
/*
+ * Implementation of 'title_window_icon <yes|no>' and 'title_window_icon padding <px>'
+ *
+ */
+void cmd_title_window_icon(I3_CMD, const char *enable, int padding) {
+ if (enable != NULL && !boolstr(enable)) {
+ padding = -1;
+ }
+ DLOG("setting window_icon=%d\n", padding);
+ HANDLE_EMPTY_MATCH;
+
+ owindow *current;
+ TAILQ_FOREACH (current, &owindows, owindows) {
+ DLOG("setting window_icon for %p / %s\n", current->con, current->con->name);
+ current->con->window_icon_padding = padding;
+
+ if (current->con->window != NULL) {
+ /* Make sure the window title is redrawn immediately. */
+ current->con->window->name_x_changed = true;
+ } else {
+ /* For windowless containers we also need to force the redrawing. */
+ FREE(current->con->deco_render_params);
+ }
+ }
+
+ cmd_output->needs_tree_render = true;
+ ysuccess(true);
+}
+
+/*
* Implementation of 'rename workspace [<name>] to <name>'
*
*/
diff --git a/src/con.c b/src/con.c
index b935bed6..18338235 100644
--- a/src/con.c
+++ b/src/con.c
@@ -43,6 +43,7 @@ Con *con_new_skeleton(Con *parent, i3Window *window) {
new->window = window;
new->border_style = config.default_border;
new->current_border_width = -1;
+ new->window_icon_padding = -1;
if (window) {
new->depth = window->depth;
} else {
diff --git a/src/config_directives.c b/src/config_directives.c
index 1e792fe0..c6b839c2 100644
--- a/src/config_directives.c
+++ b/src/config_directives.c
@@ -124,15 +124,6 @@ CFGFUN(criteria_add, const char *ctype, const char *cvalue) {
* Utility functions
******************************************************************************/
-static bool eval_boolstr(const char *str) {
- return (strcasecmp(str, "1") == 0 ||
- strcasecmp(str, "yes") == 0 ||
- strcasecmp(str, "true") == 0 ||
- strcasecmp(str, "on") == 0 ||
- strcasecmp(str, "enable") == 0 ||
- strcasecmp(str, "active") == 0);
-}
-
/*
* A utility function to convert a string containing the group and modifiers to
* the corresponding bit mask.
@@ -316,14 +307,14 @@ CFGFUN(hide_edge_borders, const char *borders) {
config.hide_edge_borders = HEBM_BOTH;
else if (strcmp(borders, "none") == 0)
config.hide_edge_borders = HEBM_NONE;
- else if (eval_boolstr(borders))
+ else if (boolstr(borders))
config.hide_edge_borders = HEBM_VERTICAL;
else
config.hide_edge_borders = HEBM_NONE;
}
CFGFUN(focus_follows_mouse, const char *value) {
- config.disable_focus_follows_mouse = !eval_boolstr(value);
+ config.disable_focus_follows_mouse = !boolstr(value);
}
CFGFUN(mouse_warping, const char *value) {
@@ -334,11 +325,11 @@ CFGFUN(mouse_warping, const char *value) {
}
CFGFUN(force_xinerama, const char *value) {
- config.force_xinerama = eval_boolstr(value);
+ config.force_xinerama = boolstr(value);
}
CFGFUN(disable_randr15, const char *value) {
- config.disable_randr15 = eval_boolstr(value);
+ config.disable_randr15 = boolstr(value);
}
CFGFUN(focus_wrapping, const char *value) {
@@ -346,7 +337,7 @@ CFGFUN(focus_wrapping, const char *value) {
config.focus_wrapping = FOCUS_WRAPPING_FORCE;
} else if (strcmp(value, "workspace") == 0) {
config.focus_wrapping = FOCUS_WRAPPING_WORKSPACE;
- } else if (eval_boolstr(value)) {
+ } else if (boolstr(value)) {
config.focus_wrapping = FOCUS_WRAPPING_ON;
} else {
config.focus_wrapping = FOCUS_WRAPPING_OFF;
@@ -355,7 +346,7 @@ CFGFUN(focus_wrapping, const char *value) {
CFGFUN(force_focus_wrapping, const char *value) {
/* Legacy syntax. */
- if (eval_boolstr(value)) {
+ if (boolstr(value)) {
config.focus_wrapping = FOCUS_WRAPPING_FORCE;
} else {
/* For "force_focus_wrapping off", don't enable or disable
@@ -367,7 +358,7 @@ CFGFUN(force_focus_wrapping, const char *value) {
}
CFGFUN(workspace_back_and_forth, const char *value) {
- config.workspace_auto_back_and_forth = eval_boolstr(value);
+ config.workspace_auto_back_and_forth = boolstr(value);
}
CFGFUN(fake_outputs, const char *outputs) {
@@ -409,7 +400,7 @@ CFGFUN(title_align, const char *alignment) {
}
CFGFUN(show_marks, const char *value) {
- config.show_marks = eval_boolstr(value);
+ config.show_marks = boolstr(value);
}
static char *current_workspace = NULL;
@@ -597,7 +588,7 @@ CFGFUN(bar_output, const char *output) {
}
CFGFUN(bar_verbose, const char *verbose) {
- current_bar->verbose = eval_boolstr(verbose);
+ current_bar->verbose = boolstr(verbose);
}
CFGFUN(bar_modifier, const char *modifiers) {
@@ -717,11 +708,11 @@ CFGFUN(bar_status_command, const char *command) {
}
CFGFUN(bar_binding_mode_indicator, const char *value) {
- current_bar->hide_binding_mode_indicator = !eval_boolstr(value);
+ current_bar->hide_binding_mode_indicator = !boolstr(value);
}
CFGFUN(bar_workspace_buttons, const char *value) {
- current_bar->hide_workspace_buttons = !eval_boolstr(value);
+ current_bar->hide_workspace_buttons = !boolstr(value);
}
CFGFUN(bar_workspace_min_width, const long width) {
@@ -729,11 +720,11 @@ CFGFUN(bar_workspace_min_width, const long width) {
}
CFGFUN(bar_strip_workspace_numbers, const char *value) {
- current_bar->strip_workspace_numbers = eval_boolstr(value);
+ current_bar->strip_workspace_numbers = boolstr(value);
}
CFGFUN(bar_strip_workspace_name, const char *value) {
- current_bar->strip_workspace_name = eval_boolstr(value);
+ current_bar->strip_workspace_name = boolstr(value);
}
CFGFUN(bar_start) {
diff --git a/src/handlers.c b/src/handlers.c
index fe967773..f8016b03 100644
--- a/src/handlers.c
+++ b/src/handlers.c
@@ -1185,6 +1185,14 @@ static bool handle_i3_floating(Con *con, xcb_get_property_reply_t *prop) {
return true;
}
+static bool handle_windowicon_change(Con *con, xcb_get_property_reply_t *prop) {
+ window_update_icon(con->window, prop);
+
+ x_push_changes(croot);
+
+ return true;
+}
+
/* Returns false if the event could not be processed (e.g. the window could not
* be found), true otherwise */
typedef bool (*cb_property_handler_t)(Con *con, xcb_get_property_reply_t *property);
@@ -1208,7 +1216,8 @@ static struct property_handler_t property_handlers[] = {
{0, UINT_MAX, handle_window_type},
{0, UINT_MAX, handle_i3_floating},
{0, 128, handle_machine_change},
- {0, 5 * sizeof(uint64_t), handle_motif_hints_change}};
+ {0, 5 * sizeof(uint64_t), handle_motif_hints_change},
+ {0, UINT_MAX, handle_windowicon_change}};
#define NUM_HANDLERS (sizeof(property_handlers) / sizeof(struct property_handler_t))
/*
@@ -1232,6 +1241,7 @@ void property_handlers_init(void) {
property_handlers[10].atom = A_I3_FLOATING_WINDOW;
property_handlers[11].atom = XCB_ATOM_WM_CLIENT_MACHINE;
property_handlers[12].atom = A__MOTIF_WM_HINTS;
+ property_handlers[13].atom = A__NET_WM_ICON;
}
static void property_notify(uint8_t state, xcb_window_t window, xcb_atom_t atom) {
diff --git a/src/ipc.c b/src/ipc.c
index b21d79a1..b50648e3 100644
--- a/src/ipc.c
+++ b/src/ipc.c
@@ -498,6 +498,9 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
ystr(con->title_format);
}
+ ystr("window_icon_padding");
+ y(integer, con->window_icon_padding);
+
if (con->type == CT_WORKSPACE) {
ystr("num");
y(integer, con->num);
diff --git a/src/load_layout.c b/src/load_layout.c
index 3d8033e8..7bdf5521 100644
--- a/src/load_layout.c
+++ b/src/load_layout.c
@@ -455,6 +455,10 @@ static int json_int(void *ctx, long long val) {
if (strcasecmp(last_key, "current_border_width") == 0)
json_node->current_border_width = val;
+ if (strcasecmp(last_key, "window_icon_padding") == 0) {
+ json_node->window_icon_padding = val;
+ }
+
if (strcasecmp(last_key, "depth") == 0)
json_node->depth = val;
diff --git a/src/manage.c b/src/manage.c
index 8ea820de..ee046f0f 100644
--- a/src/manage.c
+++ b/src/manage.c
@@ -119,6 +119,8 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
wm_normal_hints_cookie, motif_wm_hints_cookie, wm_user_time_cookie, wm_desktop_cookie,
wm_machine_cookie;
+ xcb_get_property_cookie_t wm_icon_cookie;
+
geomc = xcb_get_geometry(conn, d);
/* Check if the window is mapped (it could be not mapped when intializing and
@@ -191,6 +193,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
wm_user_time_cookie = GET_PROPERTY(A__NET_WM_USER_TIME, UINT32_MAX);
wm_desktop_cookie = GET_PROPERTY(A__NET_WM_DESKTOP, UINT32_MAX);
wm_machine_cookie = GET_PROPERTY(XCB_ATOM_WM_CLIENT_MACHINE, UINT32_MAX);
+ wm_icon_cookie = GET_PROPERTY(A__NET_WM_ICON, UINT32_MAX);
i3Window *cwindow = scalloc(1, sizeof(i3Window));
cwindow->id = window;
@@ -204,6 +207,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
window_update_class(cwindow, xcb_get_property_reply(conn, class_cookie, NULL));
window_update_name_legacy(cwindow, xcb_get_property_reply(conn, title_cookie, NULL));
window_update_name(cwindow, xcb_get_property_reply(conn, utf8_title_cookie, NULL));
+ window_update_icon(cwindow, xcb_get_property_reply(conn, wm_icon_cookie, NULL));
window_update_leader(cwindow, xcb_get_property_reply(conn, leader_cookie, NULL));
window_update_transient_for(cwindow, xcb_get_property_reply(conn, transient_cookie, NULL));
window_update_strut_partial(cwindow, xcb_get_property_reply(conn, strut_cookie, NULL));
diff --git a/src/window.c b/src/window.c
index 2c2f6c0a..c1fde4f9 100644
--- a/src/window.c
+++ b/src/window.c
@@ -19,6 +19,7 @@ void window_free(i3Window *win) {
FREE(win->class_class);
FREE(win->class_instance);
i3string_free(win->name);
+ cairo_surface_destroy(win->icon);
FREE(win->ran_assignments);
FREE(win);
}
@@ -484,3 +485,112 @@ void window_update_machine(i3Window *win, xcb_get_property_reply_t *prop) {
free(prop);
}
+
+void window_update_icon(i3Window *win, xcb_get_property_reply_t *prop) {
+ uint32_t *data = NULL;
+ uint32_t width, height;
+ uint64_t len = 0;
+ const uint32_t pref_size = (uint32_t)(render_deco_height() - logical_px(2));
+
+ if (!prop || prop->type != XCB_ATOM_CARDINAL || prop->format != 32) {
+ DLOG("_NET_WM_ICON is not set\n");
+ FREE(prop);
+ return;
+ }
+
+ uint32_t prop_value_len = xcb_get_property_value_length(prop);
+ uint32_t *prop_value = (uint32_t *)xcb_get_property_value(prop);
+
+ /* Find an icon matching the preferred size.
+ * If there is no such icon, take the smallest icon having at least
+ * the preferred size.
+ * If all icons are smaller than the preferred size, chose the largest.
+ */
+ while (prop_value_len > (sizeof(uint32_t) * 2) && prop_value &&
+ prop_value[0] && prop_value[1]) {
+ const uint32_t cur_width = prop_value[0];
+ const uint32_t cur_height = prop_value[1];
+ /* Check that the property is as long as it should be (in bytes),
+ handling integer overflow. "+2" to handle the width and height
+ fields. */
+ const uint64_t cur_len = cur_width * (uint64_t)cur_height;
+ const uint64_t expected_len = (cur_len + 2) * 4;
+
+ if (expected_len > prop_value_len) {
+ break;
+ }
+
+ DLOG("Found _NET_WM_ICON of size: (%d,%d)\n", cur_width, cur_height);
+
+ const bool at_least_preferred_size = (cur_width >= pref_size &&
+ cur_height >= pref_size);
+ const bool smaller_than_current = (cur_width < width ||
+ cur_height < height);
+ const bool larger_than_current = (cur_width > width ||
+ cur_height > height);
+ const bool not_yet_at_preferred = (width < pref_size ||
+ height < pref_size);
+ if (len == 0 ||
+ (at_least_preferred_size &&
+ (smaller_than_current || not_yet_at_preferred)) ||
+ (!at_least_preferred_size &&
+ not_yet_at_preferred &&
+ larger_than_current)) {
+ len = cur_len;
+ width = cur_width;
+ height = cur_height;
+ data = prop_value;
+ }
+
+ if (width == pref_size && height == pref_size) {
+ break;
+ }
+
+ /* Find pointer to next icon in the reply. */
+ prop_value_len -= expected_len;
+ prop_value = (uint32_t *)(((uint8_t *)prop_value) + expected_len);
+ }
+
+ if (!data) {
+ DLOG("Could not get _NET_WM_ICON\n");
+ FREE(prop);
+ return;
+ }
+
+ DLOG("Using icon of size (%d,%d) (preferred size: %d)\n",
+ width, height, pref_size);
+
+ win->name_x_changed = true; /* trigger a redraw */
+
+ uint32_t *icon = smalloc(len * 4);
+
+ for (uint64_t i = 0; i < len; i++) {
+ uint8_t r, g, b, a;
+ const uint32_t pixel = data[2 + i];
+ a = (pixel >> 24) & 0xff;
+ r = (pixel >> 16) & 0xff;
+ g = (pixel >> 8) & 0xff;
+ b = (pixel >> 0) & 0xff;
+
+ /* Cairo uses premultiplied alpha */
+ r = (r * a) / 0xff;
+ g = (g * a) / 0xff;
+ b = (b * a) / 0xff;
+
+ icon[i] = ((uint32_t)a << 24) | (r << 16) | (g << 8) | b;
+ }
+
+ if (win->icon != NULL) {
+ cairo_surface_destroy(win->icon);
+ }
+ win->icon = cairo_image_surface_create_for_data(
+ (unsigned char *)icon,
+ CAIRO_FORMAT_ARGB32,
+ width,
+ height,
+ width * 4);
+ static cairo_user_data_key_t free_data;
+ cairo_surface_set_user_data(win->icon, &free_data, icon, free);
+
+ FREE(prop);
+}
diff --git a/src/x.c b/src/x.c
index c9ef07f7..55dc63a0 100644
--- a/src/x.c
+++ b/src/x.c
@@ -612,11 +612,36 @@ void x_draw_decoration(Con *con) {
/* 5: draw title border */
x_draw_title_border(con, p);
- /* 6: draw the title */
+ /* 6: draw the icon and title */
int text_offset_y = (con->deco_rect.height - config.font.height) / 2;
+ int text_offset_x = 0;
+
+ struct Window *win = con->window;
const int title_padding = logical_px(2);
const int deco_width = (int)con->deco_rect.width;
+
+ /* Draw the icon */
+ if (con->window_icon_padding > -1 && win && win->icon) {
+ /* icon_padding is applied horizontally only,
+ * the icon will always use all available vertical space. */
+ const int icon_padding = logical_px(1 + con->window_icon_padding);
+
+ const uint16_t icon_size = con->deco_rect.height - 2 * logical_px(1);
+
+ const int icon_offset_y = logical_px(1);
+
+ text_offset_x += icon_size + 2 * icon_padding;
+
+ draw_util_image(
+ win->icon,
+ &(parent->frame_buffer),
+ con->deco_rect.x + icon_padding,
+ con->deco_rect.y + icon_offset_y,
+ icon_size,
+ icon_size);
+ }
+
int mark_width = 0;
if (config.show_marks && !TAILQ_EMPTY(&(con->marks_head))) {
char *formatted_mark = sstrdup("");
@@ -655,7 +680,6 @@ void x_draw_decoration(Con *con) {
}
i3String *title = NULL;
- struct Window *win = con->window;
if (win == NULL) {
if (con->title_format == NULL) {
char *_title;
@@ -699,9 +723,9 @@ void x_draw_decoration(Con *con) {
draw_util_text(title, &(parent->frame_buffer),
p->color->text, p->color->background,
- con->deco_rect.x + title_offset_x,
+ con->deco_rect.x + text_offset_x + title_offset_x,
con->deco_rect.y + text_offset_y,
- deco_width - mark_width - 2 * title_padding);
+ deco_width - text_offset_x - mark_width - 2 * title_padding);
if (win == NULL || con->title_format != NULL) {
I3STRING_FREE(title);
diff --git a/testcases/t/116-nestedcons.t b/testcases/t/116-nestedcons.t
index 6a2274d4..7b15c5f0 100644
--- a/testcases/t/116-nestedcons.t
+++ b/testcases/t/116-nestedcons.t
@@ -73,6 +73,7 @@ my $expected = {
workspace_layout => 'default',
current_border_width => -1,
marks => $ignore,
+ window_icon_padding => -1,
};
# a shallow copy is sufficient, since we only ignore values at the root
diff --git a/testcases/t/187-commands-parser.t b/testcases/t/187-commands-parser.t
index 2ff5e212..06dbce7e 100644
--- a/testcases/t/187-commands-parser.t
+++ b/testcases/t/187-commands-parser.t
@@ -176,6 +176,7 @@ is(parser_calls('unknown_literal'),
scratchpad
swap
title_format
+ title_window_icon
mode
bar
)) . "'\n" .
diff --git a/testcases/t/314-window-icon-padding.t b/testcases/t/314-window-icon-padding.t
new file mode 100644
index 00000000..5320ab9f
--- /dev/null
+++ b/testcases/t/314-window-icon-padding.t
@@ -0,0 +1,62 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • https://build.i3wm.org/docs/testsuite.html
+# (or docs/testsuite)
+#
+# • https://build.i3wm.org/docs/lib-i3test.html
+# (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • https://build.i3wm.org/docs/ipc.html
+# (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+# (unless you are already familiar with Perl)
+#
+# Verifies title_window_icon behavior.
+use i3test i3_autostart => 0;
+
+sub window_icon_padding {
+ my ($ws) = @_;
+ my ($nodes, $focus) = get_ws_content($ws);
+ ok(@{$nodes} == 1, 'precisely one container on workspace');
+ return $nodes->[0]->{'window_icon_padding'};
+}
+
+my $config = <<"EOT";
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+EOT
+my $pid = launch_with_config($config);
+
+my $tmp = fresh_workspace;
+
+cmd 'open';
+is(window_icon_padding($tmp), -1, 'window_icon_padding defaults to -1');
+
+cmd 'title_window_icon on';
+isnt(window_icon_padding($tmp), -1, 'window_icon_padding no longer -1');
+
+exit_gracefully($pid);
+
+################################################################################
+# Verify title_window_icon can be used with for_window as expected
+################################################################################
+
+$config = <<"EOT";
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+for_window [class=".*"] title_window_icon padding 3px
+EOT
+$pid = launch_with_config($config);
+
+$tmp = fresh_workspace;
+
+open_window;
+is(window_icon_padding($tmp), 3, 'window_icon_padding set to 3');
+
+exit_gracefully($pid);
+
+done_testing;