From 034815b8fd427428d545993b8e2461a82dd0605c Mon Sep 17 00:00:00 2001 From: =?utf8?q?Ingo=20B=C3=BCrk?= Date: Mon, 14 Sep 2015 13:34:15 +0200 Subject: [PATCH] Set and unset individual atoms in _NET_WM_STATE instead of overwriting the entire list everytime. This allows independent management of multiple states. fixes #1873 --- include/xcb.h | 15 ++++ src/con.c | 15 ++-- src/ewmh.c | 14 ++-- src/x.c | 6 +- src/xcb.c | 45 ++++++++++++ testcases/t/253-multiple-net-wm-state-atoms.t | 70 +++++++++++++++++++ 6 files changed, 146 insertions(+), 19 deletions(-) create mode 100644 testcases/t/253-multiple-net-wm-state-atoms.t diff --git a/include/xcb.h b/include/xcb.h index 7dab5d10..2c87a19c 100644 --- a/include/xcb.h +++ b/include/xcb.h @@ -145,3 +145,18 @@ uint16_t get_visual_depth(xcb_visualid_t visual_id); * */ xcb_visualid_t get_visualid_by_depth(uint16_t depth); + +/** + * Add an atom to a list of atoms the given property defines. + * This is useful, for example, for manipulating _NET_WM_STATE. + * + */ +void xcb_add_property_atom(xcb_connection_t *conn, xcb_window_t window, xcb_atom_t property, xcb_atom_t atom); + +/** + * Remove an atom from a list of atoms the given property defines without + * removing any other potentially set atoms. This is useful, for example, for + * manipulating _NET_WM_STATE. + * + */ +void xcb_remove_property_atom(xcb_connection_t *conn, xcb_window_t window, xcb_atom_t property, xcb_atom_t atom); diff --git a/src/con.c b/src/con.c index 7a8ccd58..1d2795c3 100644 --- a/src/con.c +++ b/src/con.c @@ -658,14 +658,13 @@ static void con_set_fullscreen_mode(Con *con, fullscreen_mode_t fullscreen_mode) if (con->window == NULL) return; - uint32_t values[1]; - unsigned int num = 0; - - if (con->fullscreen_mode != CF_NONE) - values[num++] = A__NET_WM_STATE_FULLSCREEN; - - xcb_change_property(conn, XCB_PROP_MODE_REPLACE, con->window->id, - A__NET_WM_STATE, XCB_ATOM_ATOM, 32, num, values); + if (con->fullscreen_mode != CF_NONE) { + DLOG("Setting _NET_WM_STATE_FULLSCREEN for con = %p / window = %d.\n", con, con->window->id); + xcb_add_property_atom(conn, con->window->id, A__NET_WM_STATE, A__NET_WM_STATE_FULLSCREEN); + } else { + DLOG("Removing _NET_WM_STATE_FULLSCREEN for con = %p / window = %d.\n", con, con->window->id); + xcb_remove_property_atom(conn, con->window->id, A__NET_WM_STATE, A__NET_WM_STATE_FULLSCREEN); + } } /* diff --git a/src/ewmh.c b/src/ewmh.c index eb6a6ea6..b2260d64 100644 --- a/src/ewmh.c +++ b/src/ewmh.c @@ -218,13 +218,13 @@ void ewmh_update_client_list_stacking(xcb_window_t *stack, int num_windows) { * */ void ewmh_update_sticky(xcb_window_t window, bool sticky) { - uint32_t values[1]; - unsigned int num = 0; - - if (sticky) - values[num++] = A__NET_WM_STATE_STICKY; - - xcb_change_property(conn, XCB_PROP_MODE_REPLACE, window, A__NET_WM_STATE, XCB_ATOM_ATOM, 32, num, values); + if (sticky) { + DLOG("Setting _NET_WM_STATE_STICKY for window = %d.\n", window); + xcb_add_property_atom(conn, window, A__NET_WM_STATE, A__NET_WM_STATE_STICKY); + } else { + DLOG("Removing _NET_WM_STATE_STICKY for window = %d.\n", window); + xcb_remove_property_atom(conn, window, A__NET_WM_STATE, A__NET_WM_STATE_STICKY); + } } /* diff --git a/src/x.c b/src/x.c index f0da9fff..bf0cb914 100644 --- a/src/x.c +++ b/src/x.c @@ -631,16 +631,14 @@ static void set_hidden_state(Con *con) { if (should_be_hidden == state->is_hidden) return; - unsigned int num = 0; - uint32_t values[1]; if (should_be_hidden) { DLOG("setting _NET_WM_STATE_HIDDEN for con = %p\n", con); - values[num++] = A__NET_WM_STATE_HIDDEN; + xcb_add_property_atom(conn, con->window->id, A__NET_WM_STATE, A__NET_WM_STATE_HIDDEN); } else { DLOG("removing _NET_WM_STATE_HIDDEN for con = %p\n", con); + xcb_remove_property_atom(conn, con->window->id, A__NET_WM_STATE, A__NET_WM_STATE_HIDDEN); } - xcb_change_property(conn, XCB_PROP_MODE_REPLACE, con->window->id, A__NET_WM_STATE, XCB_ATOM_ATOM, 32, num, values); state->is_hidden = should_be_hidden; } diff --git a/src/xcb.c b/src/xcb.c index 001f6b51..f98115f5 100644 --- a/src/xcb.c +++ b/src/xcb.c @@ -274,3 +274,48 @@ xcb_visualid_t get_visualid_by_depth(uint16_t depth) { } return 0; } + +/* + * Add an atom to a list of atoms the given property defines. + * This is useful, for example, for manipulating _NET_WM_STATE. + * + */ +void xcb_add_property_atom(xcb_connection_t *conn, xcb_window_t window, xcb_atom_t property, xcb_atom_t atom) { + xcb_change_property(conn, XCB_PROP_MODE_APPEND, window, property, XCB_ATOM_ATOM, 32, 1, (uint32_t[]){atom}); +} + +/* + * Remove an atom from a list of atoms the given property defines without + * removing any other potentially set atoms. This is useful, for example, for + * manipulating _NET_WM_STATE. + * + */ +void xcb_remove_property_atom(xcb_connection_t *conn, xcb_window_t window, xcb_atom_t property, xcb_atom_t atom) { + xcb_grab_server(conn); + + xcb_get_property_reply_t *reply = + xcb_get_property_reply(conn, + xcb_get_property(conn, false, window, property, XCB_GET_PROPERTY_TYPE_ANY, 0, 4096), NULL); + if (reply == NULL || xcb_get_property_value_length(reply) == 0) + goto release_grab; + xcb_atom_t *atoms = xcb_get_property_value(reply); + if (atoms == NULL) { + goto release_grab; + } + + { + int num = 0; + const int current_size = xcb_get_property_value_length(reply) / (reply->format / 8); + xcb_atom_t values[current_size]; + for (int i = 0; i < current_size; i++) { + if (atoms[i] != atom) + values[num++] = atoms[i]; + } + + xcb_change_property(conn, XCB_PROP_MODE_REPLACE, window, property, XCB_ATOM_ATOM, 32, num, values); + } + +release_grab: + FREE(reply); + xcb_ungrab_server(conn); +} diff --git a/testcases/t/253-multiple-net-wm-state-atoms.t b/testcases/t/253-multiple-net-wm-state-atoms.t new file mode 100644 index 00000000..0ce7f9fd --- /dev/null +++ b/testcases/t/253-multiple-net-wm-state-atoms.t @@ -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) +# +# Ticket: #1873 +use i3test; +use X11::XCB qw(:all); + +sub get_wm_state { + sync_with_i3; + my $atom = $x->atom(name => '_NET_WM_STATE_HIDDEN'); + + my ($con) = @_; + my $cookie = $x->get_property( + 0, + $con->{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}); + return \@atoms; +} + +my $wm_state_sticky = $x->atom(name => '_NET_WM_STATE_STICKY')->id; +my $wm_state_fullscreen = $x->atom(name => '_NET_WM_STATE_FULLSCREEN')->id; + +########################################################################## +# Given a sticky container, when it is fullscreened, then both wm state +# atoms are set. When the container is unfullscreened, then only the +# sticky atom is still set. +########################################################################## + +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'); + +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'); + +cmd 'sticky disable'; +is_deeply(get_wm_state($window), [ $wm_state_fullscreen ], 'only _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'); + +########################################################################## + +done_testing; -- 2.39.5