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
This commit is contained in:
Michael Stapelberg 2021-06-13 08:35:52 +02:00 committed by GitHub
parent eaa5e636f9
commit abbf6a85d7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 392 additions and 27 deletions

View File

@ -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 • i3-dump-log -f now uses UNIX sockets instead of pthreads. The UNIX socket approach
should be more reliable and also more portable. should be more reliable and also more portable.
• Implement the include config directive • Implement the include config directive
• Implement optionally showing window icons in titlebar
• Allow for_window to match against WM_CLIENT_MACHINE • Allow for_window to match against WM_CLIENT_MACHINE
• Add %machine placeholder (WM_CLIENT_MACHINE) to title_format • Add %machine placeholder (WM_CLIENT_MACHINE) to title_format
• Allow multiple output names in 'move container|workspace to output' • Allow multiple output names in 'move container|workspace to output'

View File

@ -2703,6 +2703,32 @@ for_window [class=".*"] title_format "<b>%title</b>"
for_window [class="(?i)firefox"] title_format "<span foreground='red'>%title</span>" 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 === Changing border style
To change the border of the current client, you can use +border normal+ to use the normal To change the border of the current client, you can use +border normal+ to use the normal

View File

@ -331,3 +331,9 @@ void cmd_shmlog(I3_CMD, const char *argument);
* *
*/ */
void cmd_debuglog(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);

View File

@ -15,6 +15,7 @@
#include <xcb/randr.h> #include <xcb/randr.h>
#include <pcre.h> #include <pcre.h>
#include <sys/time.h> #include <sys/time.h>
#include <cairo/cairo.h>
#include "queue.h" #include "queue.h"
@ -471,6 +472,9 @@ struct Window {
double min_aspect_ratio; double min_aspect_ratio;
double max_aspect_ratio; double max_aspect_ratio;
/** Window icon, as Cairo surface */
cairo_surface_t *icon;
/** The window has a nonrectangular shape. */ /** The window has a nonrectangular shape. */
bool shaped; bool shaped;
/** The window has a nonrectangular input shape. */ /** The window has a nonrectangular input shape. */
@ -656,6 +660,11 @@ struct Con {
/** The format with which the window's name should be displayed. */ /** The format with which the window's name should be displayed. */
char *title_format; 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 /* 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 * group. The contents are shared between all of them, that is they are
* displayed on whichever of the containers is currently visible */ * displayed on whichever of the containers is currently visible */

View File

@ -3,6 +3,7 @@
xmacro(_NET_WM_USER_TIME) \ xmacro(_NET_WM_USER_TIME) \
xmacro(_NET_STARTUP_ID) \ xmacro(_NET_STARTUP_ID) \
xmacro(_NET_WORKAREA) \ xmacro(_NET_WORKAREA) \
xmacro(_NET_WM_ICON) \
xmacro(WM_PROTOCOLS) \ xmacro(WM_PROTOCOLS) \
xmacro(WM_DELETE_WINDOW) \ xmacro(WM_DELETE_WINDOW) \
xmacro(UTF8_STRING) \ xmacro(UTF8_STRING) \

View File

@ -611,6 +611,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); 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. * Draws a filled rectangle.
* This function is a convenience wrapper and takes care of flushing the * This function is a convenience wrapper and takes care of flushing the
@ -668,3 +673,9 @@ void set_screenshot_as_wallpaper(xcb_connection_t *conn, xcb_screen_t *screen);
* content of the window. * content of the window.
*/ */
bool is_background_set(xcb_connection_t *conn, xcb_screen_t *screen); 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);

View File

@ -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); 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);

25
libi3/boolstr.c Normal file
View File

@ -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);
}

View File

@ -141,6 +141,30 @@ void draw_util_text(i3String *text, surface_t *surface, color_t fg_color, color_
cairo_surface_mark_dirty(surface->surface); 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. * Draws a filled rectangle.
* This function is a convenience wrapper and takes care of flushing the * This function is a convenience wrapper and takes care of flushing the

View File

@ -325,6 +325,7 @@ ev_dep = cc.find_library('ev')
inc = include_directories('include') inc = include_directories('include')
libi3srcs = [ libi3srcs = [
'libi3/boolstr.c',
'libi3/create_socket.c', 'libi3/create_socket.c',
'libi3/dpi.c', 'libi3/dpi.c',
'libi3/draw_util.c', 'libi3/draw_util.c',

View File

@ -40,6 +40,7 @@ state INITIAL:
'scratchpad' -> SCRATCHPAD 'scratchpad' -> SCRATCHPAD
'swap' -> SWAP 'swap' -> SWAP
'title_format' -> TITLE_FORMAT 'title_format' -> TITLE_FORMAT
'title_window_icon' -> TITLE_WINDOW_ICON
'mode' -> MODE 'mode' -> MODE
'bar' -> BAR 'bar' -> BAR
@ -462,6 +463,20 @@ state TITLE_FORMAT:
format = string format = string
-> call cmd_title_format($format) -> 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>] # bar (hidden_state hide|show|toggle)|(mode dock|hide|invisible|toggle) [<bar_id>]
state BAR: state BAR:
'hidden_state' 'hidden_state'

View File

@ -2037,6 +2037,35 @@ void cmd_title_format(I3_CMD, const char *format) {
ysuccess(true); ysuccess(true);
} }
/*
* 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>' * Implementation of 'rename workspace [<name>] to <name>'
* *

View File

@ -43,6 +43,7 @@ Con *con_new_skeleton(Con *parent, i3Window *window) {
new->window = window; new->window = window;
new->border_style = config.default_border; new->border_style = config.default_border;
new->current_border_width = -1; new->current_border_width = -1;
new->window_icon_padding = -1;
if (window) { if (window) {
new->depth = window->depth; new->depth = window->depth;
} else { } else {

View File

@ -124,15 +124,6 @@ CFGFUN(criteria_add, const char *ctype, const char *cvalue) {
* Utility functions * 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 * A utility function to convert a string containing the group and modifiers to
* the corresponding bit mask. * the corresponding bit mask.
@ -316,14 +307,14 @@ CFGFUN(hide_edge_borders, const char *borders) {
config.hide_edge_borders = HEBM_BOTH; config.hide_edge_borders = HEBM_BOTH;
else if (strcmp(borders, "none") == 0) else if (strcmp(borders, "none") == 0)
config.hide_edge_borders = HEBM_NONE; config.hide_edge_borders = HEBM_NONE;
else if (eval_boolstr(borders)) else if (boolstr(borders))
config.hide_edge_borders = HEBM_VERTICAL; config.hide_edge_borders = HEBM_VERTICAL;
else else
config.hide_edge_borders = HEBM_NONE; config.hide_edge_borders = HEBM_NONE;
} }
CFGFUN(focus_follows_mouse, const char *value) { 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) { CFGFUN(mouse_warping, const char *value) {
@ -334,11 +325,11 @@ CFGFUN(mouse_warping, const char *value) {
} }
CFGFUN(force_xinerama, 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) { CFGFUN(disable_randr15, const char *value) {
config.disable_randr15 = eval_boolstr(value); config.disable_randr15 = boolstr(value);
} }
CFGFUN(focus_wrapping, const char *value) { CFGFUN(focus_wrapping, const char *value) {
@ -346,7 +337,7 @@ CFGFUN(focus_wrapping, const char *value) {
config.focus_wrapping = FOCUS_WRAPPING_FORCE; config.focus_wrapping = FOCUS_WRAPPING_FORCE;
} else if (strcmp(value, "workspace") == 0) { } else if (strcmp(value, "workspace") == 0) {
config.focus_wrapping = FOCUS_WRAPPING_WORKSPACE; config.focus_wrapping = FOCUS_WRAPPING_WORKSPACE;
} else if (eval_boolstr(value)) { } else if (boolstr(value)) {
config.focus_wrapping = FOCUS_WRAPPING_ON; config.focus_wrapping = FOCUS_WRAPPING_ON;
} else { } else {
config.focus_wrapping = FOCUS_WRAPPING_OFF; config.focus_wrapping = FOCUS_WRAPPING_OFF;
@ -355,7 +346,7 @@ CFGFUN(focus_wrapping, const char *value) {
CFGFUN(force_focus_wrapping, const char *value) { CFGFUN(force_focus_wrapping, const char *value) {
/* Legacy syntax. */ /* Legacy syntax. */
if (eval_boolstr(value)) { if (boolstr(value)) {
config.focus_wrapping = FOCUS_WRAPPING_FORCE; config.focus_wrapping = FOCUS_WRAPPING_FORCE;
} else { } else {
/* For "force_focus_wrapping off", don't enable or disable /* 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) { 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) { CFGFUN(fake_outputs, const char *outputs) {
@ -409,7 +400,7 @@ CFGFUN(title_align, const char *alignment) {
} }
CFGFUN(show_marks, const char *value) { CFGFUN(show_marks, const char *value) {
config.show_marks = eval_boolstr(value); config.show_marks = boolstr(value);
} }
static char *current_workspace = NULL; static char *current_workspace = NULL;
@ -597,7 +588,7 @@ CFGFUN(bar_output, const char *output) {
} }
CFGFUN(bar_verbose, const char *verbose) { CFGFUN(bar_verbose, const char *verbose) {
current_bar->verbose = eval_boolstr(verbose); current_bar->verbose = boolstr(verbose);
} }
CFGFUN(bar_modifier, const char *modifiers) { 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) { 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) { 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) { 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) { 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) { 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) { CFGFUN(bar_start) {

View File

@ -1185,6 +1185,14 @@ static bool handle_i3_floating(Con *con, xcb_get_property_reply_t *prop) {
return true; 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 /* Returns false if the event could not be processed (e.g. the window could not
* be found), true otherwise */ * be found), true otherwise */
typedef bool (*cb_property_handler_t)(Con *con, xcb_get_property_reply_t *property); 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_window_type},
{0, UINT_MAX, handle_i3_floating}, {0, UINT_MAX, handle_i3_floating},
{0, 128, handle_machine_change}, {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)) #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[10].atom = A_I3_FLOATING_WINDOW;
property_handlers[11].atom = XCB_ATOM_WM_CLIENT_MACHINE; property_handlers[11].atom = XCB_ATOM_WM_CLIENT_MACHINE;
property_handlers[12].atom = A__MOTIF_WM_HINTS; 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) { static void property_notify(uint8_t state, xcb_window_t window, xcb_atom_t atom) {

View File

@ -498,6 +498,9 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
ystr(con->title_format); ystr(con->title_format);
} }
ystr("window_icon_padding");
y(integer, con->window_icon_padding);
if (con->type == CT_WORKSPACE) { if (con->type == CT_WORKSPACE) {
ystr("num"); ystr("num");
y(integer, con->num); y(integer, con->num);

View File

@ -455,6 +455,10 @@ static int json_int(void *ctx, long long val) {
if (strcasecmp(last_key, "current_border_width") == 0) if (strcasecmp(last_key, "current_border_width") == 0)
json_node->current_border_width = val; 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) if (strcasecmp(last_key, "depth") == 0)
json_node->depth = val; json_node->depth = val;

View File

@ -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_normal_hints_cookie, motif_wm_hints_cookie, wm_user_time_cookie, wm_desktop_cookie,
wm_machine_cookie; wm_machine_cookie;
xcb_get_property_cookie_t wm_icon_cookie;
geomc = xcb_get_geometry(conn, d); geomc = xcb_get_geometry(conn, d);
/* Check if the window is mapped (it could be not mapped when intializing and /* 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_user_time_cookie = GET_PROPERTY(A__NET_WM_USER_TIME, UINT32_MAX);
wm_desktop_cookie = GET_PROPERTY(A__NET_WM_DESKTOP, 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_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)); i3Window *cwindow = scalloc(1, sizeof(i3Window));
cwindow->id = window; 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_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_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_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_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_transient_for(cwindow, xcb_get_property_reply(conn, transient_cookie, NULL));
window_update_strut_partial(cwindow, xcb_get_property_reply(conn, strut_cookie, NULL)); window_update_strut_partial(cwindow, xcb_get_property_reply(conn, strut_cookie, NULL));

View File

@ -19,6 +19,7 @@ void window_free(i3Window *win) {
FREE(win->class_class); FREE(win->class_class);
FREE(win->class_instance); FREE(win->class_instance);
i3string_free(win->name); i3string_free(win->name);
cairo_surface_destroy(win->icon);
FREE(win->ran_assignments); FREE(win->ran_assignments);
FREE(win); FREE(win);
} }
@ -484,3 +485,112 @@ void window_update_machine(i3Window *win, xcb_get_property_reply_t *prop) {
free(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);
}

32
src/x.c
View File

@ -612,11 +612,36 @@ void x_draw_decoration(Con *con) {
/* 5: draw title border */ /* 5: draw title border */
x_draw_title_border(con, p); 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_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 title_padding = logical_px(2);
const int deco_width = (int)con->deco_rect.width; 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; int mark_width = 0;
if (config.show_marks && !TAILQ_EMPTY(&(con->marks_head))) { if (config.show_marks && !TAILQ_EMPTY(&(con->marks_head))) {
char *formatted_mark = sstrdup(""); char *formatted_mark = sstrdup("");
@ -655,7 +680,6 @@ void x_draw_decoration(Con *con) {
} }
i3String *title = NULL; i3String *title = NULL;
struct Window *win = con->window;
if (win == NULL) { if (win == NULL) {
if (con->title_format == NULL) { if (con->title_format == NULL) {
char *_title; char *_title;
@ -699,9 +723,9 @@ void x_draw_decoration(Con *con) {
draw_util_text(title, &(parent->frame_buffer), draw_util_text(title, &(parent->frame_buffer),
p->color->text, p->color->background, 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, 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) { if (win == NULL || con->title_format != NULL) {
I3STRING_FREE(title); I3STRING_FREE(title);

View File

@ -73,6 +73,7 @@ my $expected = {
workspace_layout => 'default', workspace_layout => 'default',
current_border_width => -1, current_border_width => -1,
marks => $ignore, marks => $ignore,
window_icon_padding => -1,
}; };
# a shallow copy is sufficient, since we only ignore values at the root # a shallow copy is sufficient, since we only ignore values at the root

View File

@ -176,6 +176,7 @@ is(parser_calls('unknown_literal'),
scratchpad scratchpad
swap swap
title_format title_format
title_window_icon
mode mode
bar bar
)) . "'\n" . )) . "'\n" .

View File

@ -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;