diff --git a/docs/userguide b/docs/userguide index 98e8dee7..9316a4d5 100644 --- a/docs/userguide +++ b/docs/userguide @@ -2120,6 +2120,12 @@ and the following placeholders which will be replaced: +%title+:: The X11 window title (_NET_WM_NAME or WM_NAME as fallback). ++%class+: + The X11 window class (second part of WM_CLASS). This corresponds to the + +class+ criterion, see <>. ++%instance+: + The X11 window instance (first part of WM_CLASS). This corresponds to the + +instance+ criterion, see <>. Using the <> directive, you can set the title format for any window based on <>. diff --git a/src/x.c b/src/x.c index cdfc0f2f..16417ffc 100644 --- a/src/x.c +++ b/src/x.c @@ -302,21 +302,39 @@ void x_window_kill(xcb_window_t window, kill_window_t kill_window) { free(event); } -static i3String *parse_title_format(char *format, i3String *_title) { +static i3String *parse_title_format(struct Window *win) { /* 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); + char *format = win->title_format; + /* We initialize these lazily so we only escape them if really necessary. */ + const char *escaped_title = NULL; + const char *escaped_class = NULL; + const char *escaped_instance = NULL; /* 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")) { + if (escaped_title == NULL) + escaped_title = i3string_as_utf8(is_markup ? i3string_escape_markup(win->name) : win->name); + buffer_len = buffer_len - strlen("%title") + strlen(escaped_title); walk += strlen("%title") - 1; + } else if (STARTS_WITH(walk, "%class")) { + if (escaped_class == NULL) + escaped_class = is_markup ? g_markup_escape_text(win->class_class, -1) : win->class_class; + + buffer_len = buffer_len - strlen("%class") + strlen(escaped_class); + walk += strlen("%class") - 1; + } else if (STARTS_WITH(walk, "%instance")) { + if (escaped_instance == NULL) + escaped_instance = is_markup ? g_markup_escape_text(win->class_instance, -1) : win->class_instance; + + buffer_len = buffer_len - strlen("%instance") + strlen(escaped_instance); + walk += strlen("%instance") - 1; } } @@ -332,6 +350,12 @@ static i3String *parse_title_format(char *format, i3String *_title) { if (STARTS_WITH(walk + 1, "title")) { outwalk += sprintf(outwalk, "%s", escaped_title); walk += strlen("title"); + } else if (STARTS_WITH(walk + 1, "class")) { + outwalk += sprintf(outwalk, "%s", escaped_class); + walk += strlen("class"); + } else if (STARTS_WITH(walk + 1, "instance")) { + outwalk += sprintf(outwalk, "%s", escaped_instance); + walk += strlen("instance"); } } *outwalk = '\0'; @@ -588,7 +612,7 @@ void x_draw_decoration(Con *con) { I3STRING_FREE(mark); } - i3String *title = win->title_format == NULL ? win->name : parse_title_format(win->title_format, win->name); + i3String *title = win->title_format == NULL ? win->name : parse_title_format(win); draw_text(title, parent->pixmap, parent->pm_gc, con->deco_rect.x + logical_px(2) + indent_px, con->deco_rect.y + text_offset_y,