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:
parent
eaa5e636f9
commit
abbf6a85d7
@ -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'
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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 */
|
||||
|
@ -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) \
|
||||
|
@ -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);
|
||||
|
||||
/**
|
||||
* 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
|
||||
@ -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);
|
||||
|
@ -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);
|
||||
|
25
libi3/boolstr.c
Normal file
25
libi3/boolstr.c
Normal 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);
|
||||
}
|
@ -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
|
||||
|
@ -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',
|
||||
|
@ -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'
|
||||
|
@ -2037,6 +2037,35 @@ void cmd_title_format(I3_CMD, const char *format) {
|
||||
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>'
|
||||
*
|
||||
|
@ -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 {
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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));
|
||||
|
110
src/window.c
110
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);
|
||||
}
|
||||
|
32
src/x.c
32
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);
|
||||
|
@ -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
|
||||
|
@ -176,6 +176,7 @@ is(parser_calls('unknown_literal'),
|
||||
scratchpad
|
||||
swap
|
||||
title_format
|
||||
title_window_icon
|
||||
mode
|
||||
bar
|
||||
)) . "'\n" .
|
||||
|
62
testcases/t/314-window-icon-padding.t
Normal file
62
testcases/t/314-window-icon-padding.t
Normal 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;
|
Loading…
x
Reference in New Issue
Block a user