diff --git a/docs/userguide b/docs/userguide index e0d38835..64f614f5 100644 --- a/docs/userguide +++ b/docs/userguide @@ -564,6 +564,8 @@ hide_edge_borders vertical === Arbitrary commands for specific windows (for_window) +[[for_window]] + With the +for_window+ command, you can let i3 execute any command when it encounters a specific window. This can be used to set windows to floating or to change their border style, for example. @@ -2083,6 +2085,37 @@ Alternatively, if you do not want to mess with +i3-input+, you could create seperate bindings for a specific set of labels and then only use those labels. /////////////////////////////////////////////////////////////////// +=== Window title format + +By default, i3 will simply print the X11 window title. Using +title_format+, +this can be customized by setting the format to the desired output. This +directive supports +https://developer.gnome.org/pango/stable/PangoMarkupFormat.html[Pango markup] +and the following placeholders which will be replaced: + ++%title+:: + The X11 window title (_NET_WM_NAME or WM_NAME as fallback). + +Using the <> directive, you can set the title format for any window +based on <>. + +*Syntax*: +--------------------- +title_format +--------------------- + +*Examples*: +------------------------------------------------------------------------------------- +# give the focused window a prefix +bindsym $mod+p title_format "Important | %title" + +# print all window titles bold +for_window [class=".*"] title_format "%title" + +# print window titles of firefox windows red +for_window [class="(?i)firefox"] title_format "%title" +------------------------------------------------------------------------------------- + === 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 16b7e146..6d1046d4 100644 --- a/include/commands.h +++ b/include/commands.h @@ -276,6 +276,12 @@ void cmd_move_scratchpad(I3_CMD); */ void cmd_scratchpad_show(I3_CMD); +/** + * Implementation of 'title_format ' + * + */ +void cmd_title_format(I3_CMD, char *format); + /** * Implementation of 'rename workspace to ' * diff --git a/include/data.h b/include/data.h index 6cb6babc..e08cdd4c 100644 --- a/include/data.h +++ b/include/data.h @@ -363,6 +363,8 @@ struct Window { /** The name of the window. */ i3String *name; + /** The format with which the window's name should be displayed. */ + char *title_format; /** The WM_WINDOW_ROLE of this window (for example, the pidgin buddy window * sets "buddy list"). Useful to match specific windows in assignments or diff --git a/include/libi3.h b/include/libi3.h index 3e6f8427..69c44528 100644 --- a/include/libi3.h +++ b/include/libi3.h @@ -243,6 +243,11 @@ bool i3string_is_markup(i3String *str); */ void i3string_set_markup(i3String *str, bool is_markup); +/** + * Escape pango markup characters in the given string. + */ +i3String *i3string_escape_markup(i3String *str); + /** * Returns the number of glyphs in an i3String. * @@ -381,6 +386,12 @@ xcb_char2b_t *convert_utf8_to_ucs2(char *input, size_t *real_strlen); */ void set_font_colors(xcb_gcontext_t gc, uint32_t foreground, uint32_t background); +/** + * Returns true if and only if the current font is a pango font. + * + */ +bool font_is_pango(void); + /** * Draws text onto the specified X drawable (normally a pixmap) at the * specified coordinates (from the top left corner of the leftmost, uppermost diff --git a/libi3/font.c b/libi3/font.c index 0f30e74e..b502b52c 100644 --- a/libi3/font.c +++ b/libi3/font.c @@ -340,6 +340,18 @@ void set_font_colors(xcb_gcontext_t gc, uint32_t foreground, uint32_t background } } +/* + * Returns true if and only if the current font is a pango font. + * + */ +bool font_is_pango(void) { +#if PANGO_SUPPORT + return savedFont->type == FONT_TYPE_PANGO; +#else + return false; +#endif +} + static int predict_text_width_xcb(const xcb_char2b_t *text, size_t text_len); static void draw_text_xcb(const xcb_char2b_t *text, size_t text_len, xcb_drawable_t drawable, diff --git a/libi3/string.c b/libi3/string.c index 28575e1f..70244743 100644 --- a/libi3/string.c +++ b/libi3/string.c @@ -13,6 +13,10 @@ #include #include +#if PANGO_SUPPORT +#include +#endif + #include "libi3.h" struct _i3String { @@ -185,6 +189,18 @@ void i3string_set_markup(i3String *str, bool is_markup) { str->is_markup = is_markup; } +/* + * Escape pango markup characters in the given string. + */ +i3String *i3string_escape_markup(i3String *str) { +#if PANGO_SUPPORT + const char *text = i3string_as_utf8(str); + return i3string_from_utf8(g_markup_escape_text(text, -1)); +#else + return str; +#endif +} + /* * Returns the number of glyphs in an i3String. * diff --git a/parser-specs/commands.spec b/parser-specs/commands.spec index c3e6e489..c756dd8b 100644 --- a/parser-specs/commands.spec +++ b/parser-specs/commands.spec @@ -37,6 +37,7 @@ state INITIAL: 'rename' -> RENAME 'nop' -> NOP 'scratchpad' -> SCRATCHPAD + 'title_format' -> TITLE_FORMAT 'mode' -> MODE 'bar' -> BAR @@ -374,6 +375,10 @@ state SCRATCHPAD: 'show' -> call cmd_scratchpad_show() +state TITLE_FORMAT: + format = string + -> call cmd_title_format($format) + # bar (hidden_state hide|show|toggle)|(mode dock|hide|invisible|toggle) [] state BAR: bar_type = 'hidden_state' diff --git a/src/commands.c b/src/commands.c index 44b910ec..71b48182 100644 --- a/src/commands.c +++ b/src/commands.c @@ -1899,6 +1899,35 @@ void cmd_scratchpad_show(I3_CMD) { ysuccess(true); } +/* + * Implementation of 'title_format ' + * + */ +void cmd_title_format(I3_CMD, char *format) { + DLOG("setting title_format to \"%s\"\n", format); + HANDLE_EMPTY_MATCH; + + owindow *current; + TAILQ_FOREACH(current, &owindows, owindows) { + if (current->con->window == NULL) + continue; + + DLOG("setting title_format for %p / %s\n", current->con, current->con->name); + FREE(current->con->window->title_format); + + /* If we only display the title without anything else, we can skip the parsing step, + * so we remove the title format altogether. */ + if (strcasecmp(format, "%title") != 0) + current->con->window->title_format = sstrdup(format); + + /* Make sure the window title is redrawn immediately. */ + current->con->window->name_x_changed = true; + } + + cmd_output->needs_tree_render = true; + ysuccess(true); +} + /* * Implementation of 'rename workspace [] to ' * diff --git a/src/x.c b/src/x.c index 2dcffe6b..ef6c7341 100644 --- a/src/x.c +++ b/src/x.c @@ -302,6 +302,45 @@ void x_window_kill(xcb_window_t window, kill_window_t kill_window) { free(event); } +static i3String *parse_title_format(char *format, i3String *_title) { + /* We need to ensure that we only escape the window title if pango + * is used by the current font. */ + const bool is_markup = font_is_pango(); + + i3String *title = is_markup ? i3string_escape_markup(_title) : _title; + const char *escaped_title = i3string_as_utf8(title); + + /* We have to first iterate over the string to see how much buffer space + * we need to allocate. */ + int buffer_len = strlen(format) + 1; + for (char *walk = format; *walk != '\0'; walk++) { + if (STARTS_WITH(walk, "%title")) { + buffer_len = buffer_len - strlen("%title") + strlen(escaped_title); + walk += strlen("%title") - 1; + } + } + + /* Now we can parse the format string. */ + char buffer[buffer_len]; + char *outwalk = buffer; + for (char *walk = format; *walk != '\0'; walk++) { + if (*walk != '%') { + *(outwalk++) = *walk; + continue; + } + + if (STARTS_WITH(walk + 1, "title")) { + outwalk += sprintf(outwalk, "%s", escaped_title); + walk += strlen("title"); + } + } + *outwalk = '\0'; + + i3String *formatted = i3string_from_utf8(buffer); + i3string_set_markup(formatted, is_markup); + return formatted; +} + /* * Draws the decoration of the given container onto its parent. * @@ -549,10 +588,13 @@ void x_draw_decoration(Con *con) { I3STRING_FREE(mark); } - draw_text(win->name, + i3String *title = win->title_format == NULL ? win->name : parse_title_format(win->title_format, win->name); + draw_text(title, parent->pixmap, parent->pm_gc, con->deco_rect.x + logical_px(2) + indent_px, con->deco_rect.y + text_offset_y, con->deco_rect.width - logical_px(2) - indent_px - mark_width - logical_px(2)); + if (win->title_format != NULL) + I3STRING_FREE(title); after_title: /* Since we don’t clip the text at all, it might in some cases be painted diff --git a/testcases/t/187-commands-parser.t b/testcases/t/187-commands-parser.t index 094caeaa..fc7fa882 100644 --- a/testcases/t/187-commands-parser.t +++ b/testcases/t/187-commands-parser.t @@ -144,7 +144,7 @@ is(parser_calls("\nworkspace test"), ################################################################################ is(parser_calls('unknown_literal'), - "ERROR: Expected one of these tokens: , '[', 'move', 'exec', 'exit', 'restart', 'reload', 'shmlog', 'debuglog', 'border', 'layout', 'append_layout', 'workspace', 'focus', 'kill', 'open', 'fullscreen', 'split', 'floating', 'mark', 'unmark', 'resize', 'rename', 'nop', 'scratchpad', 'mode', 'bar'\n" . + "ERROR: Expected one of these tokens: , '[', 'move', 'exec', 'exit', 'restart', 'reload', 'shmlog', 'debuglog', 'border', 'layout', 'append_layout', 'workspace', 'focus', 'kill', 'open', 'fullscreen', 'split', 'floating', 'mark', 'unmark', 'resize', 'rename', 'nop', 'scratchpad', 'title_format', 'mode', 'bar'\n" . "ERROR: Your command: unknown_literal\n" . "ERROR: ^^^^^^^^^^^^^^^", 'error for unknown literal ok');