From 9cd4b5323180d97ca68e5d7ff7772ccf2789e334 Mon Sep 17 00:00:00 2001 From: Tony Crisci Date: Sun, 24 Jul 2016 20:43:09 -0400 Subject: [PATCH 1/2] testcases: remove assumption from state atoms test Remove the assumption that only two atoms can possibly be set in t/253-multiple-net-wm-state-atoms.t so that the tests will pass when more atoms are supported that may be set during this test. --- testcases/t/253-multiple-net-wm-state-atoms.t | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/testcases/t/253-multiple-net-wm-state-atoms.t b/testcases/t/253-multiple-net-wm-state-atoms.t index 392beae1..3a3e7c6e 100644 --- a/testcases/t/253-multiple-net-wm-state-atoms.t +++ b/testcases/t/253-multiple-net-wm-state-atoms.t @@ -36,7 +36,7 @@ sub get_wm_state { return undef if $len == 0; my @atoms = unpack("L$len", $reply->{value}); - return \@atoms; + return @atoms; } my $wm_state_sticky = $x->atom(name => '_NET_WM_STATE_STICKY')->id; @@ -51,18 +51,24 @@ my $wm_state_fullscreen = $x->atom(name => '_NET_WM_STATE_FULLSCREEN')->id; fresh_workspace; my $window = open_window; cmd 'sticky enable'; -is_deeply(get_wm_state($window), [ $wm_state_sticky ], 'sanity check: _NET_WM_STATE_STICKY is set'); +my @state = get_wm_state($window); +ok((scalar grep { $_ == $wm_state_sticky } @state) > 0, 'sanity check: _NET_WM_STATE_STICKY is set'); cmd 'fullscreen enable'; -is_deeply(get_wm_state($window), [ $wm_state_sticky, $wm_state_fullscreen ], - 'both _NET_WM_STATE_FULLSCREEN and _NET_WM_STATE_STICKY are set'); +@state = get_wm_state($window); +ok((scalar grep { $_ == $wm_state_sticky } @state) > 0, '_NET_WM_STATE_STICKY is set'); +ok((scalar grep { $_ == $wm_state_fullscreen } @state) > 0, '_NET_WM_STATE_FULLSCREEN is set'); cmd 'sticky disable'; -is_deeply(get_wm_state($window), [ $wm_state_fullscreen ], 'only _NET_WM_STATE_FULLSCREEN is set'); +@state = get_wm_state($window); +ok((scalar grep { $_ == $wm_state_sticky } @state) == 0, '_NET_WM_STATE_STICKY is not set'); +ok((scalar grep { $_ == $wm_state_fullscreen } @state) > 0, '_NET_WM_STATE_FULLSCREEN is set'); cmd 'sticky enable'; cmd 'fullscreen disable'; -is_deeply(get_wm_state($window), [ $wm_state_sticky ], 'only _NET_WM_STATE_STICKY is set'); +@state = get_wm_state($window); +ok((scalar grep { $_ == $wm_state_sticky } @state) > 0, '_NET_WM_STATE_STICKY is set'); +ok((scalar grep { $_ == $wm_state_fullscreen } @state) == 0, '_NET_WM_STATE_FULLSCREEN is not set'); ############################################################################### # _NET_WM_STATE is removed when the window is withdrawn. @@ -71,7 +77,8 @@ is_deeply(get_wm_state($window), [ $wm_state_sticky ], 'only _NET_WM_STATE_STICK fresh_workspace; $window = open_window; cmd 'sticky enable'; -is_deeply(get_wm_state($window), [ $wm_state_sticky ], 'sanity check: _NET_WM_STATE_STICKY is set'); +@state = get_wm_state($window); +ok((scalar grep { $_ == $wm_state_sticky } @state) > 0, '_NET_WM_STATE_STICKY is set'); $window->unmap; wait_for_unmap($window); From c42de09b1b0b0bcf96342d662f4bef3011b8ce5d Mon Sep 17 00:00:00 2001 From: Tony Crisci Date: Sun, 24 Jul 2016 20:43:56 -0400 Subject: [PATCH 2/2] Support _NET_WM_STATE_FOCUSED _NET_WM_STATE_FOCUSED is set on _NET_WM_STATE to indicate that the window is focused. It must be set when the window is newly focused and removed once the window no longer has focus. > _NET_WM_STATE_FOCUSED indicates whether the window's decorations are > drawn in an active state. Clients MUST regard it as a read-only hint. > It cannot be set at map time or changed via a _NET_WM_STATE client > message. For example, this is used by GTK applications to show the decoration in an active or inactive state. This change can be tested by opening a GTK application (like evince), focusing the window and unfocusing the window, and observing a change in the window decorations. Fixes #2273 --- include/atoms_NET_SUPPORTED.xmacro | 1 + include/ewmh.h | 6 +++++ src/ewmh.c | 14 ++++++++++ src/x.c | 24 ++++++++++++++--- testcases/lib/i3test.pm.in | 35 ++++++++++++++++++++++++ testcases/t/158-wm_take_focus.t | 3 +++ testcases/t/295-net-wm-state-focused.t | 37 ++++++++++++++++++++++++++ 7 files changed, 117 insertions(+), 3 deletions(-) create mode 100644 testcases/t/295-net-wm-state-focused.t diff --git a/include/atoms_NET_SUPPORTED.xmacro b/include/atoms_NET_SUPPORTED.xmacro index a7b9676d..a81948a9 100644 --- a/include/atoms_NET_SUPPORTED.xmacro +++ b/include/atoms_NET_SUPPORTED.xmacro @@ -8,6 +8,7 @@ xmacro(_NET_WM_STATE_FULLSCREEN) xmacro(_NET_WM_STATE_DEMANDS_ATTENTION) xmacro(_NET_WM_STATE_MODAL) xmacro(_NET_WM_STATE_HIDDEN) +xmacro(_NET_WM_STATE_FOCUSED) xmacro(_NET_WM_STATE) xmacro(_NET_WM_WINDOW_TYPE) xmacro(_NET_WM_WINDOW_TYPE_NORMAL) diff --git a/include/ewmh.h b/include/ewmh.h index 5844faa6..01ae67f9 100644 --- a/include/ewmh.h +++ b/include/ewmh.h @@ -83,6 +83,12 @@ void ewmh_update_client_list_stacking(xcb_window_t *stack, int num_windows); */ void ewmh_update_sticky(xcb_window_t window, bool sticky); +/** + * Set or remove _NEW_WM_STATE_FOCUSED on the window. + * + */ +void ewmh_update_focused(xcb_window_t window, bool is_focused); + /** * Set up the EWMH hints on the root window. * diff --git a/src/ewmh.c b/src/ewmh.c index f8422bda..e5dcafcb 100644 --- a/src/ewmh.c +++ b/src/ewmh.c @@ -284,6 +284,20 @@ void ewmh_update_sticky(xcb_window_t window, bool sticky) { } } +/* + * Set or remove _NEW_WM_STATE_FOCUSED on the window. + * + */ +void ewmh_update_focused(xcb_window_t window, bool is_focused) { + if (is_focused) { + DLOG("Setting _NET_WM_STATE_FOCUSED for window = %d.\n", window); + xcb_add_property_atom(conn, window, A__NET_WM_STATE, A__NET_WM_STATE_FOCUSED); + } else { + DLOG("Removing _NET_WM_STATE_FOCUSED for window = %d.\n", window); + xcb_remove_property_atom(conn, window, A__NET_WM_STATE, A__NET_WM_STATE_FOCUSED); + } +} + /* * Set up the EWMH hints on the root window. * diff --git a/src/x.c b/src/x.c index 7829079b..629520d4 100644 --- a/src/x.c +++ b/src/x.c @@ -99,6 +99,23 @@ static con_state *state_for_frame(xcb_window_t window) { return NULL; } +/* + * Changes the atoms on the root window and the windows themselves to properly + * reflect the current focus for ewmh compliance. + * + */ +static void change_ewmh_focus(xcb_window_t new_focus, xcb_window_t old_focus) { + ewmh_update_active_window(new_focus); + + if (new_focus != XCB_WINDOW_NONE) { + ewmh_update_focused(new_focus, true); + } + + if (old_focus != XCB_WINDOW_NONE) { + ewmh_update_focused(old_focus, false); + } +} + /* * Initializes the X11 part for the given container. Called exactly once for * every container from con_new(). @@ -1120,7 +1137,7 @@ void x_push_changes(Con *con) { to_focus, focused, focused->name); send_take_focus(to_focus, last_timestamp); - ewmh_update_active_window((con_has_managed_window(focused) ? focused->window->id : XCB_WINDOW_NONE)); + change_ewmh_focus((con_has_managed_window(focused) ? focused->window->id : XCB_WINDOW_NONE), last_focused); if (to_focus != last_focused && is_con_attached(focused)) ipc_send_window_event("focus", focused); @@ -1139,7 +1156,7 @@ void x_push_changes(Con *con) { xcb_change_window_attributes(conn, focused->window->id, XCB_CW_EVENT_MASK, values); } - ewmh_update_active_window((con_has_managed_window(focused) ? focused->window->id : XCB_WINDOW_NONE)); + change_ewmh_focus((con_has_managed_window(focused) ? focused->window->id : XCB_WINDOW_NONE), last_focused); if (to_focus != XCB_NONE && to_focus != last_focused && focused->window != NULL && is_con_attached(focused)) ipc_send_window_event("focus", focused); @@ -1154,7 +1171,8 @@ void x_push_changes(Con *con) { * root window in order to avoid an X11 fallback mechanism causing a ghosting effect (see #1378). */ DLOG("Still no window focused, better set focus to the EWMH support window (%d)\n", ewmh_window); xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, ewmh_window, last_timestamp); - ewmh_update_active_window(XCB_WINDOW_NONE); + change_ewmh_focus(XCB_WINDOW_NONE, last_focused); + focused_id = ewmh_window; } diff --git a/testcases/lib/i3test.pm.in b/testcases/lib/i3test.pm.in index e754c0c1..68ac1ee5 100644 --- a/testcases/lib/i3test.pm.in +++ b/testcases/lib/i3test.pm.in @@ -51,6 +51,7 @@ our @EXPORT = qw( kill_all_windows events_for listen_for_binding + is_net_wm_state_focused ); =head1 NAME @@ -1026,6 +1027,40 @@ sub listen_for_binding { return $command; } +=head2 is_net_wm_state_focused + +Returns true if the given window has the _NET_WM_STATE_FOCUSED atom. + + ok(is_net_wm_state_focused($window), '_NET_WM_STATE_FOCUSED set'); + +=cut +sub is_net_wm_state_focused { + my ($window) = @_; + + sync_with_i3; + my $atom = $x->atom(name => '_NET_WM_STATE_FOCUSED'); + my $cookie = $x->get_property( + 0, + $window->{id}, + $x->atom(name => '_NET_WM_STATE')->id, + GET_PROPERTY_TYPE_ANY, + 0, + 4096 + ); + + my $reply = $x->get_property_reply($cookie->{sequence}); + my $len = $reply->{length}; + return 0 if $len == 0; + + my @atoms = unpack("L$len", $reply->{value}); + for (my $i = 0; $i < $len; $i++) { + return 1 if $atoms[$i] == $atom->id; + } + + return 0; +} + + =head1 AUTHOR Michael Stapelberg diff --git a/testcases/t/158-wm_take_focus.t b/testcases/t/158-wm_take_focus.t index 41d400d5..c4b42964 100644 --- a/testcases/t/158-wm_take_focus.t +++ b/testcases/t/158-wm_take_focus.t @@ -55,6 +55,7 @@ subtest 'Window without WM_TAKE_FOCUS', sub { my $window = open_window; ok(!recv_take_focus($window), 'did not receive ClientMessage'); + ok(is_net_wm_state_focused($window), '_NET_WM_STATE_FOCUSED set'); my ($nodes) = get_ws_content($ws); my $con = shift @$nodes; @@ -91,6 +92,7 @@ subtest 'Window with WM_TAKE_FOCUS and without InputHint', sub { $window->map; ok(!recv_take_focus($window), 'did not receive ClientMessage'); + ok(is_net_wm_state_focused($window), '_NET_WM_STATE_FOCUSED set'); my ($nodes) = get_ws_content($ws); my $con = shift @$nodes; @@ -112,6 +114,7 @@ subtest 'Window with WM_TAKE_FOCUS and unspecified InputHint', sub { my $window = open_window({ protocols => [ $take_focus ] }); ok(!recv_take_focus($window), 'did not receive ClientMessage'); + ok(is_net_wm_state_focused($window), '_NET_WM_STATE_FOCUSED set'); my ($nodes) = get_ws_content($ws); my $con = shift @$nodes; diff --git a/testcases/t/295-net-wm-state-focused.t b/testcases/t/295-net-wm-state-focused.t new file mode 100644 index 00000000..1881154e --- /dev/null +++ b/testcases/t/295-net-wm-state-focused.t @@ -0,0 +1,37 @@ +#!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 for setting and removing the _NET_WM_STATE_FOCUSED atom properly. +# Ticket: #2273 +use i3test; +use X11::XCB qw(:all); + +my ($windowA, $windowB); + +fresh_workspace; +$windowA = open_window; + +ok(is_net_wm_state_focused($windowA), 'a newly opened window that is focused should have _NET_WM_STATE_FOCUSED set'); + +$windowB = open_window; + +ok(!is_net_wm_state_focused($windowA), 'when a another window is focused, the old window should not have _NET_WM_STATE_FOCUSED set'); + +fresh_workspace; + +ok(!is_net_wm_state_focused($windowB), 'when focus moves to the ewmh support window, neither window should have _NET_WM_STATE_FOCUSED set'); + +done_testing;