Skip to content
This repository has been archived by the owner on Apr 18, 2023. It is now read-only.

Commit

Permalink
Implement showing window icons in titlebar (#4439)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
stapelberg committed Jun 13, 2021
1 parent eaa5e63 commit abbf6a8
Show file tree
Hide file tree
Showing 23 changed files with 392 additions and 27 deletions.
1 change: 1 addition & 0 deletions RELEASE-NOTES-next
Expand Up @@ -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'
Expand Down
26 changes: 26 additions & 0 deletions docs/userguide
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions include/commands.h
Expand Up @@ -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);
9 changes: 9 additions & 0 deletions include/data.h
Expand Up @@ -15,6 +15,7 @@
#include <xcb/randr.h>
#include <pcre.h>
#include <sys/time.h>
#include <cairo/cairo.h>

#include "queue.h"

Expand Down Expand Up @@ -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. */
Expand Down Expand Up @@ -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 */
Expand Down
1 change: 1 addition & 0 deletions include/i3-atoms_rest.xmacro.h
Expand Up @@ -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) \
Expand Down
11 changes: 11 additions & 0 deletions include/libi3.h
Expand Up @@ -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
Expand Down Expand Up @@ -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);
6 changes: 6 additions & 0 deletions include/window.h
Expand Up @@ -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 changes: 25 additions & 0 deletions 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);
}
24 changes: 24 additions & 0 deletions libi3/draw_util.c
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions meson.build
Expand Up @@ -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',
Expand Down
15 changes: 15 additions & 0 deletions parser-specs/commands.spec
Expand Up @@ -40,6 +40,7 @@ state INITIAL:
'scratchpad' -> SCRATCHPAD
'swap' -> SWAP
'title_format' -> TITLE_FORMAT
'title_window_icon' -> TITLE_WINDOW_ICON
'mode' -> MODE
'bar' -> BAR

Expand Down Expand Up @@ -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'
Expand Down
29 changes: 29 additions & 0 deletions src/commands.c
Expand Up @@ -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>'
*
Expand Down
1 change: 1 addition & 0 deletions src/con.c
Expand Up @@ -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 {
Expand Down
35 changes: 13 additions & 22 deletions src/config_directives.c
Expand Up @@ -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.
Expand Down Expand Up @@ -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) {
Expand All @@ -334,19 +325,19 @@ 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) {
if (strcmp(value, "force") == 0) {
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;
Expand All @@ -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
Expand All @@ -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) {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -717,23 +708,23 @@ 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) {
current_bar->workspace_min_width = 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) {
Expand Down

0 comments on commit abbf6a8

Please sign in to comment.