Support _NET_WM_VISIBLE_NAME. As per specification this is necessary since we can display custom titles with title_format.
fixes #1872
This commit is contained in:
parent
b68a400abf
commit
f44c87685b
|
@ -1,6 +1,7 @@
|
||||||
xmacro(_NET_SUPPORTED)
|
xmacro(_NET_SUPPORTED)
|
||||||
xmacro(_NET_SUPPORTING_WM_CHECK)
|
xmacro(_NET_SUPPORTING_WM_CHECK)
|
||||||
xmacro(_NET_WM_NAME)
|
xmacro(_NET_WM_NAME)
|
||||||
|
xmacro(_NET_WM_VISIBLE_NAME)
|
||||||
xmacro(_NET_WM_MOVERESIZE)
|
xmacro(_NET_WM_MOVERESIZE)
|
||||||
xmacro(_NET_WM_STATE_FULLSCREEN)
|
xmacro(_NET_WM_STATE_FULLSCREEN)
|
||||||
xmacro(_NET_WM_STATE_DEMANDS_ATTENTION)
|
xmacro(_NET_WM_STATE_DEMANDS_ATTENTION)
|
||||||
|
|
|
@ -45,6 +45,12 @@ void ewmh_update_desktop_viewport(void);
|
||||||
*/
|
*/
|
||||||
void ewmh_update_active_window(xcb_window_t window);
|
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.
|
* Updates the _NET_CLIENT_LIST hint. Used for window listers.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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);
|
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);
|
||||||
|
|
|
@ -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,
|
/* If we only display the title without anything else, we can skip the parsing step,
|
||||||
* so we remove the title format altogether. */
|
* so we remove the title format altogether. */
|
||||||
if (strcasecmp(format, "%title") != 0)
|
if (strcasecmp(format, "%title") != 0) {
|
||||||
current->con->window->title_format = sstrdup(format);
|
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. */
|
/* Make sure the window title is redrawn immediately. */
|
||||||
current->con->window->name_x_changed = true;
|
current->con->window->name_x_changed = true;
|
||||||
}
|
}
|
||||||
|
|
16
src/ewmh.c
16
src/ewmh.c
|
@ -148,6 +148,18 @@ void ewmh_update_active_window(xcb_window_t window) {
|
||||||
A__NET_ACTIVE_WINDOW, XCB_ATOM_WINDOW, 32, 1, &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
|
* i3 currently does not support _NET_WORKAREA, because it does not correspond
|
||||||
* to i3’s concept of workspaces. See also:
|
* 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. */
|
/* 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");
|
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 */
|
/* 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, 30, supported_atoms);
|
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_SUPPORTED, XCB_ATOM_ATOM, 32, /* number of atoms */ 31, supported_atoms);
|
||||||
}
|
}
|
||||||
|
|
75
src/window.c
75
src/window.c
|
@ -66,6 +66,8 @@ void window_update_name(i3Window *win, xcb_get_property_reply_t *prop, bool befo
|
||||||
i3string_free(win->name);
|
i3string_free(win->name);
|
||||||
win->name = i3string_from_utf8_with_length(xcb_get_property_value(prop),
|
win->name = i3string_from_utf8_with_length(xcb_get_property_value(prop),
|
||||||
xcb_get_property_value_length(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;
|
win->name_x_changed = true;
|
||||||
LOG("_NET_WM_NAME changed to \"%s\"\n", i3string_as_utf8(win->name));
|
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);
|
i3string_free(win->name);
|
||||||
win->name = i3string_from_utf8_with_length(xcb_get_property_value(prop),
|
win->name = i3string_from_utf8_with_length(xcb_get_property_value(prop),
|
||||||
xcb_get_property_value_length(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("WM_NAME changed to \"%s\"\n", i3string_as_utf8(win->name));
|
||||||
LOG("Using legacy window title. Note that in order to get Unicode window "
|
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_BORDER
|
||||||
#undef MWM_DECOR_TITLE
|
#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;
|
||||||
|
}
|
||||||
|
|
65
src/x.c
65
src/x.c
|
@ -302,69 +302,6 @@ void x_window_kill(xcb_window_t window, kill_window_t kill_window) {
|
||||||
free(event);
|
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.
|
* Draws the decoration of the given container onto its parent.
|
||||||
*
|
*
|
||||||
|
@ -612,7 +549,7 @@ void x_draw_decoration(Con *con) {
|
||||||
I3STRING_FREE(mark);
|
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,
|
draw_text(title,
|
||||||
parent->pixmap, parent->pm_gc,
|
parent->pixmap, parent->pm_gc,
|
||||||
con->deco_rect.x + logical_px(2) + indent_px, con->deco_rect.y + text_offset_y,
|
con->deco_rect.x + logical_px(2) + indent_px, con->deco_rect.y + text_offset_y,
|
||||||
|
|
|
@ -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;
|
Loading…
Reference in New Issue