mirror of https://github.com/i3/i3
Add support for _NET_WM_STATE_MAXIMIZED_{HORZ, VERT} (#5840)
If a window occupies the entirety of its workspace vertically and/or horizontally, pass it the _NET_WM_STATE_MAXIMIZED_{HORZ, VERT} atoms. This helps applications like Google Chrome draw the tab bar correctly and handle tab clicks correctly (see https://crbug.com/1495853). This change is based on work from @yshui in #2380.
This commit is contained in:
parent
9aba43119b
commit
b660d6a902
|
@ -83,6 +83,22 @@ bool con_is_split(Con *con);
|
||||||
*/
|
*/
|
||||||
bool con_is_hidden(Con *con);
|
bool con_is_hidden(Con *con);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the container is maximized in the given orientation.
|
||||||
|
*
|
||||||
|
* If the container is floating or fullscreen, it is not considered maximized.
|
||||||
|
* Otherwise, it is maximized if it doesn't share space with any other
|
||||||
|
* container in the given orientation. For example, if a workspace contains
|
||||||
|
* a single splitv container with three children, none of them are considered
|
||||||
|
* vertically maximized, but they are all considered horizontally maximized.
|
||||||
|
*
|
||||||
|
* Passing "maximized" hints to the application can help it make the right
|
||||||
|
* choices about how to draw its borders. See discussion in
|
||||||
|
* https://github.com/i3/i3/pull/2380.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
bool con_is_maximized(Con *con, orientation_t orientation);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether the container or any of its children is sticky.
|
* Returns whether the container or any of its children is sticky.
|
||||||
*
|
*
|
||||||
|
|
|
@ -11,6 +11,8 @@ xmacro(_NET_WM_STATE_DEMANDS_ATTENTION) \
|
||||||
xmacro(_NET_WM_STATE_MODAL) \
|
xmacro(_NET_WM_STATE_MODAL) \
|
||||||
xmacro(_NET_WM_STATE_HIDDEN) \
|
xmacro(_NET_WM_STATE_HIDDEN) \
|
||||||
xmacro(_NET_WM_STATE_FOCUSED) \
|
xmacro(_NET_WM_STATE_FOCUSED) \
|
||||||
|
xmacro(_NET_WM_STATE_MAXIMIZED_VERT) \
|
||||||
|
xmacro(_NET_WM_STATE_MAXIMIZED_HORZ) \
|
||||||
xmacro(_NET_WM_STATE) \
|
xmacro(_NET_WM_STATE) \
|
||||||
xmacro(_NET_WM_WINDOW_TYPE) \
|
xmacro(_NET_WM_WINDOW_TYPE) \
|
||||||
xmacro(_NET_WM_WINDOW_TYPE_NORMAL) \
|
xmacro(_NET_WM_WINDOW_TYPE_NORMAL) \
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
pass _NET_WM_STATE_MAXIMIZED_{HORZ, VERT} to tiled windows
|
59
src/con.c
59
src/con.c
|
@ -419,6 +419,65 @@ bool con_is_hidden(Con *con) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns true if the container is maximized in the given orientation.
|
||||||
|
*
|
||||||
|
* If the container is floating or fullscreen, it is not considered maximized.
|
||||||
|
* Otherwise, it is maximized if it doesn't share space with any other
|
||||||
|
* container in the given orientation. For example, if a workspace contains
|
||||||
|
* a single splitv container with three children, none of them are considered
|
||||||
|
* vertically maximized, but they are all considered horizontally maximized.
|
||||||
|
*
|
||||||
|
* Passing "maximized" hints to the application can help it make the right
|
||||||
|
* choices about how to draw its borders. See discussion in
|
||||||
|
* https://github.com/i3/i3/pull/2380.
|
||||||
|
*/
|
||||||
|
bool con_is_maximized(Con *con, orientation_t orientation) {
|
||||||
|
/* Fullscreen containers are not considered maximized. */
|
||||||
|
if (con->fullscreen_mode != CF_NONE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Look up the container layout which corresponds to the given
|
||||||
|
* orientation. */
|
||||||
|
layout_t layout;
|
||||||
|
switch (orientation) {
|
||||||
|
case HORIZ:
|
||||||
|
layout = L_SPLITH;
|
||||||
|
break;
|
||||||
|
case VERT:
|
||||||
|
layout = L_SPLITV;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Go through all parents, stopping once we reach the workspace node. */
|
||||||
|
Con *current = con;
|
||||||
|
while (true) {
|
||||||
|
Con *parent = current->parent;
|
||||||
|
if (parent == NULL || parent->type == CT_WORKSPACE) {
|
||||||
|
/* We are done searching. We found no reason that the container
|
||||||
|
* should not be considered maximized. */
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parent->layout == layout && con_num_children(parent) > 1) {
|
||||||
|
/* The parent has a split in the indicated direction, which
|
||||||
|
* means none of its children are maximized in that direction. */
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Floating containers and their children are not considered
|
||||||
|
* maximized. */
|
||||||
|
if (parent->type == CT_FLOATING_CON) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
current = parent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Returns whether the container or any of its children is sticky.
|
* Returns whether the container or any of its children is sticky.
|
||||||
*
|
*
|
||||||
|
|
41
src/x.c
41
src/x.c
|
@ -41,6 +41,8 @@ typedef struct con_state {
|
||||||
bool unmap_now;
|
bool unmap_now;
|
||||||
bool child_mapped;
|
bool child_mapped;
|
||||||
bool is_hidden;
|
bool is_hidden;
|
||||||
|
bool is_maximized_vert;
|
||||||
|
bool is_maximized_horz;
|
||||||
|
|
||||||
/* The con for which this state is. */
|
/* The con for which this state is. */
|
||||||
Con *con;
|
Con *con;
|
||||||
|
@ -816,6 +818,44 @@ static void set_hidden_state(Con *con) {
|
||||||
state->is_hidden = should_be_hidden;
|
state->is_hidden = should_be_hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Sets or removes _NET_WM_STATE_MAXIMIZE_{HORZ, VERT} on con
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
static void set_maximized_state(Con *con) {
|
||||||
|
if (!con->window) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
con_state *state = state_for_frame(con->frame.id);
|
||||||
|
|
||||||
|
const bool con_maximized_horz = con_is_maximized(con, HORIZ);
|
||||||
|
if (con_maximized_horz != state->is_maximized_horz) {
|
||||||
|
DLOG("setting _NET_WM_STATE_MAXIMIZED_HORZ for con %p(%s) to %d\n", con, con->name, con_maximized_horz);
|
||||||
|
|
||||||
|
if (con_maximized_horz) {
|
||||||
|
xcb_add_property_atom(conn, con->window->id, A__NET_WM_STATE, A__NET_WM_STATE_MAXIMIZED_HORZ);
|
||||||
|
} else {
|
||||||
|
xcb_remove_property_atom(conn, con->window->id, A__NET_WM_STATE, A__NET_WM_STATE_MAXIMIZED_HORZ);
|
||||||
|
}
|
||||||
|
|
||||||
|
state->is_maximized_horz = con_maximized_horz;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool con_maximized_vert = con_is_maximized(con, VERT);
|
||||||
|
if (con_maximized_vert != state->is_maximized_vert) {
|
||||||
|
DLOG("setting _NET_WM_STATE_MAXIMIZED_VERT for con %p(%s) to %d\n", con, con->name, con_maximized_vert);
|
||||||
|
|
||||||
|
if (con_maximized_vert) {
|
||||||
|
xcb_add_property_atom(conn, con->window->id, A__NET_WM_STATE, A__NET_WM_STATE_MAXIMIZED_VERT);
|
||||||
|
} else {
|
||||||
|
xcb_remove_property_atom(conn, con->window->id, A__NET_WM_STATE, A__NET_WM_STATE_MAXIMIZED_VERT);
|
||||||
|
}
|
||||||
|
|
||||||
|
state->is_maximized_vert = con_maximized_vert;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Set the container frame shape as the union of the window shape and the
|
* Set the container frame shape as the union of the window shape and the
|
||||||
* shape of the frame borders.
|
* shape of the frame borders.
|
||||||
|
@ -1121,6 +1161,7 @@ void x_push_node(Con *con) {
|
||||||
}
|
}
|
||||||
|
|
||||||
set_hidden_state(con);
|
set_hidden_state(con);
|
||||||
|
set_maximized_state(con);
|
||||||
|
|
||||||
/* Handle all children and floating windows of this node. We recurse
|
/* Handle all children and floating windows of this node. We recurse
|
||||||
* in focus order to display the focused client in a stack first when
|
* in focus order to display the focused client in a stack first when
|
||||||
|
|
|
@ -53,7 +53,7 @@ our @EXPORT = qw(
|
||||||
kill_all_windows
|
kill_all_windows
|
||||||
events_for
|
events_for
|
||||||
listen_for_binding
|
listen_for_binding
|
||||||
is_net_wm_state_focused
|
net_wm_state_contains
|
||||||
cmp_tree
|
cmp_tree
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1090,18 +1090,19 @@ sub listen_for_binding {
|
||||||
return $command;
|
return $command;
|
||||||
}
|
}
|
||||||
|
|
||||||
=head2 is_net_wm_state_focused
|
=head2 net_wm_state_contains
|
||||||
|
|
||||||
Returns true if the given window has the _NET_WM_STATE_FOCUSED atom.
|
Returns true if the given window has the given _NET_WM_STATE atom.
|
||||||
|
|
||||||
ok(is_net_wm_state_focused($window), '_NET_WM_STATE_FOCUSED set');
|
ok(net_wm_state_contains($window, '_NET_WM_STATE_FOCUSED'),
|
||||||
|
'_NET_WM_STATE_FOCUSED set');
|
||||||
|
|
||||||
=cut
|
=cut
|
||||||
sub is_net_wm_state_focused {
|
sub net_wm_state_contains {
|
||||||
my ($window) = @_;
|
my ($window, $atom_name) = @_;
|
||||||
|
|
||||||
sync_with_i3;
|
sync_with_i3;
|
||||||
my $atom = $x->atom(name => '_NET_WM_STATE_FOCUSED');
|
my $atom = $x->atom(name => $atom_name);
|
||||||
my $cookie = $x->get_property(
|
my $cookie = $x->get_property(
|
||||||
0,
|
0,
|
||||||
$window->{id},
|
$window->{id},
|
||||||
|
|
|
@ -55,8 +55,7 @@ subtest 'Window without WM_TAKE_FOCUS', sub {
|
||||||
my $window = open_window;
|
my $window = open_window;
|
||||||
|
|
||||||
ok(!recv_take_focus($window), 'did not receive ClientMessage');
|
ok(!recv_take_focus($window), 'did not receive ClientMessage');
|
||||||
ok(is_net_wm_state_focused($window), '_NET_WM_STATE_FOCUSED set');
|
ok(net_wm_state_contains($window, '_NET_WM_STATE_FOCUSED'), '_NET_WM_STATE_FOCUSED set');
|
||||||
|
|
||||||
my ($nodes) = get_ws_content($ws);
|
my ($nodes) = get_ws_content($ws);
|
||||||
my $con = shift @$nodes;
|
my $con = shift @$nodes;
|
||||||
ok($con->{focused}, 'con is focused');
|
ok($con->{focused}, 'con is focused');
|
||||||
|
@ -92,7 +91,7 @@ subtest 'Window with WM_TAKE_FOCUS and without InputHint', sub {
|
||||||
$window->map;
|
$window->map;
|
||||||
|
|
||||||
ok(!recv_take_focus($window), 'did not receive ClientMessage');
|
ok(!recv_take_focus($window), 'did not receive ClientMessage');
|
||||||
ok(is_net_wm_state_focused($window), '_NET_WM_STATE_FOCUSED set');
|
ok(net_wm_state_contains($window, '_NET_WM_STATE_FOCUSED'), '_NET_WM_STATE_FOCUSED set');
|
||||||
|
|
||||||
my ($nodes) = get_ws_content($ws);
|
my ($nodes) = get_ws_content($ws);
|
||||||
my $con = shift @$nodes;
|
my $con = shift @$nodes;
|
||||||
|
@ -114,7 +113,7 @@ subtest 'Window with WM_TAKE_FOCUS and unspecified InputHint', sub {
|
||||||
my $window = open_window({ protocols => [ $take_focus ] });
|
my $window = open_window({ protocols => [ $take_focus ] });
|
||||||
|
|
||||||
ok(!recv_take_focus($window), 'did not receive ClientMessage');
|
ok(!recv_take_focus($window), 'did not receive ClientMessage');
|
||||||
ok(is_net_wm_state_focused($window), '_NET_WM_STATE_FOCUSED set');
|
ok(net_wm_state_contains($window, '_NET_WM_STATE_FOCUSED'), '_NET_WM_STATE_FOCUSED set');
|
||||||
|
|
||||||
my ($nodes) = get_ws_content($ws);
|
my ($nodes) = get_ws_content($ws);
|
||||||
my $con = shift @$nodes;
|
my $con = shift @$nodes;
|
||||||
|
|
|
@ -20,29 +20,8 @@ use i3test;
|
||||||
use X11::XCB qw(:all);
|
use X11::XCB qw(:all);
|
||||||
|
|
||||||
sub is_hidden {
|
sub is_hidden {
|
||||||
sync_with_i3;
|
|
||||||
my $atom = $x->atom(name => '_NET_WM_STATE_HIDDEN');
|
|
||||||
|
|
||||||
my ($con) = @_;
|
my ($con) = @_;
|
||||||
my $cookie = $x->get_property(
|
return net_wm_state_contains($con, '_NET_WM_STATE_HIDDEN');
|
||||||
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});
|
|
||||||
for (my $i = 0; $i < $len; $i++) {
|
|
||||||
return 1 if $atoms[$i] == $atom->id;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
my ($tabA, $tabB, $tabC, $subtabA, $subtabB, $windowA, $windowB);
|
my ($tabA, $tabB, $tabC, $subtabA, $subtabB, $windowA, $windowB);
|
||||||
|
|
|
@ -23,17 +23,22 @@ my ($windowA, $windowB);
|
||||||
|
|
||||||
fresh_workspace;
|
fresh_workspace;
|
||||||
$windowA = open_window;
|
$windowA = open_window;
|
||||||
ok(is_net_wm_state_focused($windowA), 'a newly opened window that is focused should have _NET_WM_STATE_FOCUSED set');
|
ok(net_wm_state_contains($windowA, '_NET_WM_STATE_FOCUSED'),
|
||||||
|
'a newly opened window that is focused should have _NET_WM_STATE_FOCUSED set');
|
||||||
|
|
||||||
$windowB = open_window;
|
$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');
|
ok(!net_wm_state_contains($windowA, '_NET_WM_STATE_FOCUSED'),
|
||||||
ok(is_net_wm_state_focused($windowB), 'a newly opened window that is focused should have _NET_WM_STATE_FOCUSED set');
|
'when a another window is focused, the old window should not have _NET_WM_STATE_FOCUSED set');
|
||||||
|
ok(net_wm_state_contains($windowB, '_NET_WM_STATE_FOCUSED'),
|
||||||
|
'a newly opened window that is focused should have _NET_WM_STATE_FOCUSED set');
|
||||||
|
|
||||||
# See issue #3495.
|
# See issue #3495.
|
||||||
cmd 'kill';
|
cmd 'kill';
|
||||||
ok(is_net_wm_state_focused($windowA), 'when the second window is closed, the first window should have _NET_WM_STATE_FOCUSED set');
|
ok(net_wm_state_contains($windowA, '_NET_WM_STATE_FOCUSED'),
|
||||||
|
'when the second window is closed, the first window should have _NET_WM_STATE_FOCUSED set');
|
||||||
|
|
||||||
fresh_workspace;
|
fresh_workspace;
|
||||||
ok(!is_net_wm_state_focused($windowA), 'when focus moves to the ewmh support window, no window should have _NET_WM_STATE_FOCUSED set');
|
ok(!net_wm_state_contains($windowA, '_NET_WM_STATE_FOCUSED'),
|
||||||
|
'when focus moves to the ewmh support window, no window should have _NET_WM_STATE_FOCUSED set');
|
||||||
|
|
||||||
done_testing;
|
done_testing;
|
||||||
|
|
|
@ -0,0 +1,104 @@
|
||||||
|
#!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)
|
||||||
|
#
|
||||||
|
# • https://i3wm.org/downloads/modern_perl_a4.pdf
|
||||||
|
# (unless you are already familiar with Perl)
|
||||||
|
#
|
||||||
|
# Tests for setting and removing the _NET_WM_STATE_MAXIMIZED_VERT and
|
||||||
|
# _NET_WM_STATE_MAXIMIZED_HORZ atoms.
|
||||||
|
use i3test;
|
||||||
|
use X11::XCB qw(:all);
|
||||||
|
|
||||||
|
sub maximized_vert {
|
||||||
|
my ($window) = @_;
|
||||||
|
return net_wm_state_contains($window, '_NET_WM_STATE_MAXIMIZED_VERT');
|
||||||
|
}
|
||||||
|
|
||||||
|
sub maximized_horz {
|
||||||
|
my ($window) = @_;
|
||||||
|
return net_wm_state_contains($window, '_NET_WM_STATE_MAXIMIZED_HORZ');
|
||||||
|
}
|
||||||
|
|
||||||
|
# Returns true if the given window is maximized in both directions.
|
||||||
|
sub maximized_both {
|
||||||
|
my ($window) = @_;
|
||||||
|
return maximized_vert($window) && maximized_horz($window);
|
||||||
|
}
|
||||||
|
|
||||||
|
# Returns true if the given window is maximized in neither direction.
|
||||||
|
sub maximized_neither {
|
||||||
|
my ($window) = @_;
|
||||||
|
return !maximized_vert($window) && !maximized_horz($window);
|
||||||
|
}
|
||||||
|
|
||||||
|
my ($winA, $winB, $winC);
|
||||||
|
fresh_workspace;
|
||||||
|
|
||||||
|
$winA = open_window;
|
||||||
|
ok(maximized_both($winA), 'if there is just one window, it is maximized');
|
||||||
|
|
||||||
|
cmd 'fullscreen enable';
|
||||||
|
ok(maximized_neither($winA), 'fullscreen windows are not maximized');
|
||||||
|
|
||||||
|
cmd 'fullscreen disable';
|
||||||
|
ok(maximized_both($winA), 'disabling fullscreen sets maximized to true again');
|
||||||
|
|
||||||
|
cmd 'floating enable';
|
||||||
|
ok(maximized_neither($winA), 'floating windows are not maximized');
|
||||||
|
|
||||||
|
cmd 'floating disable';
|
||||||
|
ok(maximized_both($winA), 'disabling floating sets maximized to true again');
|
||||||
|
|
||||||
|
# Open a second window.
|
||||||
|
$winB = open_window;
|
||||||
|
|
||||||
|
# Windows in stacked or tabbed containers are considered maximized.
|
||||||
|
cmd 'layout stacking';
|
||||||
|
ok(maximized_both($winA) && maximized_both($winB),
|
||||||
|
'stacking layout maximizes all windows');
|
||||||
|
|
||||||
|
cmd 'layout tabbed';
|
||||||
|
ok(maximized_both($winA) && maximized_both($winB),
|
||||||
|
'tabbed layout maximizes all windows');
|
||||||
|
|
||||||
|
# Arrange the two windows with a vertical split.
|
||||||
|
cmd 'layout splitv';
|
||||||
|
ok(!maximized_vert($winA) && !maximized_vert($winB),
|
||||||
|
'vertical split means children are not maximized vertically');
|
||||||
|
ok(maximized_horz($winA) && maximized_horz($winB),
|
||||||
|
'children may still be maximized horizontally in a vertical split');
|
||||||
|
|
||||||
|
# Arrange the two windows with a horizontal split.
|
||||||
|
cmd 'layout splith';
|
||||||
|
ok(maximized_vert($winA) && maximized_vert($winB),
|
||||||
|
'children may still be maximized vertically in a horizontal split');
|
||||||
|
ok(!maximized_horz($winA) && !maximized_horz($winB),
|
||||||
|
'horizontal split means children are not maximized horizontally');
|
||||||
|
|
||||||
|
# Add a vertical split within the horizontal split, and open a third window.
|
||||||
|
cmd 'split vertical';
|
||||||
|
$winC = open_window;
|
||||||
|
ok(maximized_vert($winA), 'winA still reaches from top to bottom');
|
||||||
|
ok(!maximized_vert($winB) && !maximized_vert($winC),
|
||||||
|
'winB and winC are split vertically, so they are not maximized vertically');
|
||||||
|
ok(!maximized_horz($winA) && !maximized_horz($winB) && !maximized_horz($winC),
|
||||||
|
'horizontal split means children are not maximized horizontally');
|
||||||
|
|
||||||
|
# Change the vertical split container to a tabbed container.
|
||||||
|
cmd 'layout tabbed';
|
||||||
|
ok(maximized_vert($winA) && maximized_vert($winB) && maximized_vert($winC),
|
||||||
|
'all windows now reach from top to bottom');
|
||||||
|
ok(!maximized_horz($winA) && !maximized_horz($winB) && !maximized_horz($winC),
|
||||||
|
'horizontal split means children are not maximized horizontally');
|
||||||
|
|
||||||
|
done_testing;
|
Loading…
Reference in New Issue