]> git.sur5r.net Git - i3/i3/commitdiff
Support _NET_WM_VISIBLE_NAME. As per specification this is necessary since we can... 1877/head
authorIngo Bürk <ingo.buerk@tngtech.com>
Sun, 30 Aug 2015 08:10:37 +0000 (10:10 +0200)
committerIngo Bürk <ingo.buerk@tngtech.com>
Sun, 30 Aug 2015 20:42:14 +0000 (22:42 +0200)
fixes #1872

include/atoms.xmacro
include/ewmh.h
include/window.h
src/commands.c
src/ewmh.c
src/window.c
src/x.c
testcases/t/251-ewmh-visible-name.t [new file with mode: 0644]

index 00a346db7b74fff780754c497b6bbb9b7ce17939..80e3bbf09a96dee763dc1bf34cc35ffd907eb291 100644 (file)
@@ -1,6 +1,7 @@
 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)
index 8fb7902a7d7adc911a19a0ddf4bc903f290de207..d94b7f3a364d1727416300b4b32e4ad8a64f874d 100644 (file)
@@ -45,6 +45,12 @@ void ewmh_update_desktop_viewport(void);
  */
 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.
  */
index 7a248277abd09cf92fa564998f07602544d066f9..395c9883d702c983a5c79bb5ea9f4407b38b1572 100644 (file)
@@ -81,3 +81,10 @@ void window_update_hints(i3Window *win, xcb_get_property_reply_t *prop, bool *ur
  *
  */
 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);
index 62adcc6536ab87b36dd02c45ce50d555e1292200..dc440f4a26d653763c9324720557d83aca6c2042 100644 (file)
@@ -1922,9 +1922,17 @@ void cmd_title_format(I3_CMD, char *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)
+        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;
     }
index a1d2489ee60d93aef759dcf00343c4cc4446ea31..70cf77189b9cfcea4c861e328482030d56b40c72 100644 (file)
@@ -148,6 +148,18 @@ void ewmh_update_active_window(xcb_window_t window) {
                         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:
@@ -234,6 +246,6 @@ void ewmh_setup_hints(void) {
     /* 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);
 }
index 764cfca5a98267a2f6ae8467aebd9a6d025b66d3..278918c74eb42ecf4b829a4adfb3009a756ce4be 100644 (file)
@@ -66,6 +66,8 @@ void window_update_name(i3Window *win, xcb_get_property_reply_t *prop, bool befo
     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));
 
@@ -104,6 +106,8 @@ void window_update_name_legacy(i3Window *win, xcb_get_property_reply_t *prop, bo
     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 "
@@ -329,3 +333,74 @@ void window_update_motif_hints(i3Window *win, xcb_get_property_reply_t *prop, bo
 #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;
+}
diff --git a/src/x.c b/src/x.c
index 16417ffcbf7869e09984a4cd1cb1fce381d9b528..337e268c3a4f7f8436b5b8c6e6340865c8159e89 100644 (file)
--- a/src/x.c
+++ b/src/x.c
@@ -302,69 +302,6 @@ void x_window_kill(xcb_window_t window, kill_window_t kill_window) {
     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.
  *
@@ -612,7 +549,7 @@ void x_draw_decoration(Con *con) {
         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,
diff --git a/testcases/t/251-ewmh-visible-name.t b/testcases/t/251-ewmh-visible-name.t
new file mode 100644 (file)
index 0000000..c201b39
--- /dev/null
@@ -0,0 +1,70 @@
+#!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;