From 819bc093756caa27a0e2fa655e99fb11f189591b Mon Sep 17 00:00:00 2001 From: Tony Crisci Date: Tue, 8 Apr 2014 14:27:40 -0400 Subject: [PATCH] Maintain the _NET_CLIENT_LIST property Add and update the _NET_CLIENT_LIST property on the root window to better comply with ewmh standards. Information on this property can be found here: http://standards.freedesktop.org/wm-spec/latest/ar01s03.html > These arrays contain all X Windows managed by the Window Manager. > _NET_CLIENT_LIST has initial mapping order, starting with the oldest window. fixes #1099 --- include/atoms.xmacro | 1 + include/ewmh.h | 5 ++ src/ewmh.c | 18 +++++- src/x.c | 43 ++++++++++---- testcases/t/223-net-client-list.t | 99 +++++++++++++++++++++++++++++++ 5 files changed, 153 insertions(+), 13 deletions(-) create mode 100644 testcases/t/223-net-client-list.t diff --git a/include/atoms.xmacro b/include/atoms.xmacro index 86c6e562..90b02616 100644 --- a/include/atoms.xmacro +++ b/include/atoms.xmacro @@ -13,6 +13,7 @@ xmacro(_NET_WM_WINDOW_TYPE_TOOLBAR) xmacro(_NET_WM_WINDOW_TYPE_SPLASH) xmacro(_NET_WM_DESKTOP) xmacro(_NET_WM_STRUT_PARTIAL) +xmacro(_NET_CLIENT_LIST) xmacro(_NET_CLIENT_LIST_STACKING) xmacro(_NET_CURRENT_DESKTOP) xmacro(_NET_ACTIVE_WINDOW) diff --git a/include/ewmh.h b/include/ewmh.h index 9cc589d1..46d6c985 100644 --- a/include/ewmh.h +++ b/include/ewmh.h @@ -27,6 +27,11 @@ void ewmh_update_current_desktop(void); */ void ewmh_update_active_window(xcb_window_t window); +/** + * Updates the _NET_CLIENT_LIST hint. Used for window listers. + */ +void ewmh_update_client_list(xcb_window_t *list, int num_windows); + /** * Updates the _NET_CLIENT_LIST_STACKING hint. Necessary to move tabs in * Chromium correctly. diff --git a/src/ewmh.c b/src/ewmh.c index 331df5dd..16c98581 100644 --- a/src/ewmh.c +++ b/src/ewmh.c @@ -71,6 +71,22 @@ void ewmh_update_workarea(void) { xcb_delete_property(conn, root, A__NET_WORKAREA); } +/* + * Updates the _NET_CLIENT_LIST hint. + * + */ +void ewmh_update_client_list(xcb_window_t *list, int num_windows) { + xcb_change_property( + conn, + XCB_PROP_MODE_REPLACE, + root, + A__NET_CLIENT_LIST, + XCB_ATOM_WINDOW, + 32, + num_windows, + list); +} + /* * Updates the _NET_CLIENT_LIST_STACKING hint. * @@ -122,5 +138,5 @@ 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"); - xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_SUPPORTED, XCB_ATOM_ATOM, 32, 18, supported_atoms); + xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_SUPPORTED, XCB_ATOM_ATOM, 32, 19, supported_atoms); } diff --git a/src/x.c b/src/x.c index eca95da0..0088ab88 100644 --- a/src/x.c +++ b/src/x.c @@ -20,11 +20,6 @@ xcb_window_t focused_id = XCB_NONE; * tell whether the focused window actually changed. */ static xcb_window_t last_focused = XCB_NONE; -/* The bottom-to-top window stack of all windows which are managed by i3. - * Used for x_get_window_stack(). */ -static xcb_window_t *btt_stack; -static int btt_stack_num; - /* Stores coordinates to warp mouse pointer to if set */ static Rect *warp_to; @@ -60,6 +55,7 @@ typedef struct con_state { CIRCLEQ_ENTRY(con_state) state; CIRCLEQ_ENTRY(con_state) old_state; + TAILQ_ENTRY(con_state) initial_mapping_order; } con_state; CIRCLEQ_HEAD(state_head, con_state) state_head = @@ -68,6 +64,9 @@ CIRCLEQ_HEAD(state_head, con_state) state_head = CIRCLEQ_HEAD(old_state_head, con_state) old_state_head = CIRCLEQ_HEAD_INITIALIZER(old_state_head); +TAILQ_HEAD(initial_mapping_head, con_state) initial_mapping_head = + TAILQ_HEAD_INITIALIZER(initial_mapping_head); + /* * Returns the container state for the given frame. This function always * returns a container state (otherwise, there is a bug in the code and the @@ -151,8 +150,10 @@ void x_con_init(Con *con, uint16_t depth) { state->id = con->frame; state->mapped = false; state->initial = true; + DLOG("Adding window 0x%08x to lists\n", state->id); CIRCLEQ_INSERT_HEAD(&state_head, state, state); CIRCLEQ_INSERT_HEAD(&old_state_head, state, old_state); + TAILQ_INSERT_TAIL(&initial_mapping_head, state, initial_mapping_order); DLOG("adding new state for window id 0x%08x\n", state->id); } @@ -233,6 +234,7 @@ void x_con_kill(Con *con) { state = state_for_frame(con->frame); CIRCLEQ_REMOVE(&state_head, state, state); CIRCLEQ_REMOVE(&old_state_head, state, old_state); + TAILQ_REMOVE(&initial_mapping_head, state, initial_mapping_order); FREE(state->name); free(state); @@ -909,12 +911,17 @@ void x_push_changes(Con *con) { if (state->con && state->con->window) cnt++; - if (cnt != btt_stack_num) { - btt_stack = srealloc(btt_stack, sizeof(xcb_window_t) * cnt); - btt_stack_num = cnt; + /* The bottom-to-top window stack of all windows which are managed by i3. + * Used for x_get_window_stack(). */ + static xcb_window_t *client_list_windows = NULL; + static int client_list_count = 0; + + if (cnt != client_list_count) { + client_list_windows = srealloc(client_list_windows, sizeof(xcb_window_t) * cnt); + client_list_count = cnt; } - xcb_window_t *walk = btt_stack; + xcb_window_t *walk = client_list_windows; /* X11 correctly represents the stack if we push it from bottom to top */ CIRCLEQ_FOREACH_REVERSE(state, &state_head, state) { @@ -940,9 +947,21 @@ void x_push_changes(Con *con) { } /* If we re-stacked something (or a new window appeared), we need to update - * the _NET_CLIENT_LIST_STACKING hint */ - if (stacking_changed) - ewmh_update_client_list_stacking(btt_stack, btt_stack_num); + * the _NET_CLIENT_LIST and _NET_CLIENT_LIST_STACKING hints */ + if (stacking_changed) { + DLOG("Client list changed (%i clients)\n", cnt); + ewmh_update_client_list_stacking(client_list_windows, client_list_count); + + walk = client_list_windows; + + /* reorder by initial mapping */ + TAILQ_FOREACH(state, &initial_mapping_head, initial_mapping_order) { + if (state->con && state->con->window) + *walk++ = state->con->window->id; + } + + ewmh_update_client_list(client_list_windows, client_list_count); + } DLOG("PUSHING CHANGES\n"); x_push_node(con); diff --git a/testcases/t/223-net-client-list.t b/testcases/t/223-net-client-list.t new file mode 100644 index 00000000..74bd2cdf --- /dev/null +++ b/testcases/t/223-net-client-list.t @@ -0,0 +1,99 @@ +#!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) +# +# Test that _NET_CLIENT_LIST is properly updated on the root window as windows +# are mapped and unmapped. +# +# Information on this property can be found here: +# http://standards.freedesktop.org/wm-spec/latest/ar01s03.html +# +# > These arrays contain all X Windows managed by the Window Manager. +# > _NET_CLIENT_LIST has initial mapping order, starting with the oldest window. +# +# Ticket: #1099 +# Bug still in: 4.7.2-8-ge6cce92 +use i3test; + +sub get_client_list { + my $cookie = $x->get_property( + 0, + $x->get_root_window(), + $x->atom(name => '_NET_CLIENT_LIST')->id, + $x->atom(name => 'WINDOW')->id, + 0, + 4096, + ); + my $reply = $x->get_property_reply($cookie->{sequence}); + my $len = $reply->{length}; + + return () if $len == 0; + return unpack("L$len", $reply->{value}); +} + +# Mapping a window should give us one client in _NET_CLIENT_LIST +my $win1 = open_window; + +my @clients = get_client_list; + +is(@clients, 1, 'One client in _NET_CLIENT_LIST'); +is($clients[0], $win1->{id}, 'Correct client in position one'); + +# Mapping another window should give us two clients in the list with the last +# client mapped in the last position +my $win2 = open_window; + +@clients = get_client_list; +is(@clients, 2, 'Added mapped client to list (2)'); +is($clients[0], $win1->{id}, 'Correct client in position one'); +is($clients[1], $win2->{id}, 'Correct client in position two'); + +# Mapping another window should give us three clients in the list in the order +# they were mapped +my $win3 = open_window; + +@clients = get_client_list; +is(@clients, 3, 'Added mapped client to list (3)'); +is($clients[0], $win1->{id}, 'Correct client in position one'); +is($clients[1], $win2->{id}, 'Correct client in position two'); +is($clients[2], $win3->{id}, 'Correct client in position three'); + +# Unmapping the second window should give us the two remaining clients in the +# order they were mapped +$win2->unmap; +wait_for_unmap($win2); + +@clients = get_client_list; +is(@clients, 2, 'Removed unmapped client from list (2)'); +is($clients[0], $win1->{id}, 'Correct client in position one'); +is($clients[1], $win3->{id}, 'Correct client in position two'); + +# Unmapping the first window should give us only the remaining mapped window in +# the list +$win1->unmap; +wait_for_unmap($win1); + +@clients = get_client_list; +is(@clients, 1, 'Removed unmapped client from list (1)'); +is($clients[0], $win3->{id}, 'Correct client in position one'); + +# Unmapping the last window should give us an empty list +$win3->unmap; +wait_for_unmap($win3); + +@clients = get_client_list; +is(@clients, 0, 'Removed unmapped client from list (0)'); + +done_testing; -- 2.39.5