xmacro(_NET_SUPPORTED)
xmacro(_NET_SUPPORTING_WM_CHECK)
xmacro(_NET_WM_NAME)
+xmacro(_NET_WM_VISIBLE_NAME)
xmacro(_NET_WM_MOVERESIZE)
xmacro(_NET_WM_STATE_FULLSCREEN)
xmacro(_NET_WM_STATE_DEMANDS_ATTENTION)
*/
void ewmh_update_active_window(xcb_window_t window);
+/**
+ * Updates _NET_WM_VISIBLE_NAME.
+ *
+ */
+void ewmh_update_visible_name(xcb_window_t window, const char *name);
+
/**
* Updates the _NET_CLIENT_LIST hint. Used for window listers.
*/
*
*/
void window_update_motif_hints(i3Window *win, xcb_get_property_reply_t *prop, border_style_t *motif_border_style);
+
+/**
+ * Returns the window title considering the current title format.
+ * If no format is set, this will simply return the window's name.
+ *
+ */
+i3String *window_parse_title_format(i3Window *win);
/* 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)
+ if (strcasecmp(format, "%title") != 0) {
current->con->window->title_format = sstrdup(format);
+ i3String *formatted_title = window_parse_title_format(current->con->window);
+ ewmh_update_visible_name(current->con->window->id, i3string_as_utf8(formatted_title));
+ I3STRING_FREE(formatted_title);
+ } else {
+ /* We can remove _NET_WM_VISIBLE_NAME since we don't display a custom title. */
+ ewmh_update_visible_name(current->con->window->id, NULL);
+ }
+
/* Make sure the window title is redrawn immediately. */
current->con->window->name_x_changed = true;
}
A__NET_ACTIVE_WINDOW, XCB_ATOM_WINDOW, 32, 1, &window);
}
+/*
+ * Updates _NET_WM_VISIBLE_NAME.
+ *
+ */
+void ewmh_update_visible_name(xcb_window_t window, const char *name) {
+ if (name != NULL) {
+ xcb_change_property(conn, XCB_PROP_MODE_REPLACE, window, A__NET_WM_VISIBLE_NAME, A_UTF8_STRING, 8, strlen(name), name);
+ } else {
+ xcb_delete_property(conn, window, A__NET_WM_VISIBLE_NAME);
+ }
+}
+
/*
* i3 currently does not support _NET_WORKAREA, because it does not correspond
* to i3’s concept of workspaces. See also:
/* I’m not entirely sure if we need to keep _NET_WM_NAME on root. */
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_WM_NAME, A_UTF8_STRING, 8, strlen("i3"), "i3");
- /* only send the first 30 atoms (last one is _NET_CLOSE_WINDOW) increment that number when adding supported atoms */
- xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_SUPPORTED, XCB_ATOM_ATOM, 32, 30, supported_atoms);
+ /* only send the first 31 atoms (last one is _NET_CLOSE_WINDOW) increment that number when adding supported atoms */
+ xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_SUPPORTED, XCB_ATOM_ATOM, 32, /* number of atoms */ 31, supported_atoms);
}
i3string_free(win->name);
win->name = i3string_from_utf8_with_length(xcb_get_property_value(prop),
xcb_get_property_value_length(prop));
+ if (win->title_format != NULL)
+ ewmh_update_visible_name(win->id, i3string_as_utf8(window_parse_title_format(win)));
win->name_x_changed = true;
LOG("_NET_WM_NAME changed to \"%s\"\n", i3string_as_utf8(win->name));
i3string_free(win->name);
win->name = i3string_from_utf8_with_length(xcb_get_property_value(prop),
xcb_get_property_value_length(prop));
+ if (win->title_format != NULL)
+ ewmh_update_visible_name(win->id, i3string_as_utf8(window_parse_title_format(win)));
LOG("WM_NAME changed to \"%s\"\n", i3string_as_utf8(win->name));
LOG("Using legacy window title. Note that in order to get Unicode window "
#undef MWM_DECOR_BORDER
#undef MWM_DECOR_TITLE
}
+
+/*
+ * Returns the window title considering the current title format.
+ * If no format is set, this will simply return the window's name.
+ *
+ */
+i3String *window_parse_title_format(i3Window *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();
+
+ char *format = win->title_format;
+ if (format == NULL)
+ return i3string_copy(win->name);
+
+ /* 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;
+ }
+ }
+
+ /* 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");
+ } 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';
+
+ i3String *formatted = i3string_from_utf8(buffer);
+ i3string_set_markup(formatted, is_markup);
+ return formatted;
+}
free(event);
}
-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();
-
- 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;
- }
- }
-
- /* 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");
- } 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';
-
- i3String *formatted = i3string_from_utf8(buffer);
- i3string_set_markup(formatted, is_markup);
- return formatted;
-}
-
/*
* Draws the decoration of the given container onto its parent.
*
I3STRING_FREE(mark);
}
- i3String *title = win->title_format == NULL ? win->name : parse_title_format(win);
+ i3String *title = win->title_format == NULL ? win->name : window_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,
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+# (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+# (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://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)
+#
+# Tests that _NET_WM_VISIBLE_NAME is set correctly.
+# Ticket: #1872
+use i3test;
+use X11::XCB qw(:all);
+
+my ($con);
+
+sub get_visible_name {
+ sync_with_i3;
+ my ($con) = @_;
+
+ my $cookie = $x->get_property(
+ 0,
+ $con->{id},
+ $x->atom(name => '_NET_WM_VISIBLE_NAME')->id,
+ $x->atom(name => 'UTF8_STRING')->id,
+ 0,
+ 4096
+ );
+
+ my $reply = $x->get_property_reply($cookie->{sequence});
+ return undef if $reply->{value_len} == 0;
+ return $reply->{value};
+}
+
+###############################################################################
+# 1: _NET_WM_VISIBLE_NAME is set when the title format of a window is changed.
+###############################################################################
+
+fresh_workspace;
+$con = open_window(name => 'boring title');
+is(get_visible_name($con), undef, 'sanity check: initially no visible name is set');
+
+cmd 'title_format custom';
+is(get_visible_name($con), 'custom', 'the visible name is updated');
+
+cmd 'title_format "<s>%title</s>"';
+is(get_visible_name($con), '<s>boring title</s>', 'markup is returned as is');
+
+###############################################################################
+# 2: _NET_WM_VISIBLE_NAME is removed if not needed.
+###############################################################################
+
+fresh_workspace;
+$con = open_window(name => 'boring title');
+cmd 'title_format custom';
+is(get_visible_name($con), 'custom', 'sanity check: a visible name is set');
+
+cmd 'title_format %title';
+is(get_visible_name($con), undef, 'the visible name is removed again');
+
+###############################################################################
+
+done_testing;