blob: 933067e662a0ec84a1fec5f60b2d1763690b99cf [file]
/*
* Copyright © 2019 Canonical Ltd.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Authored by: William Wold <william.wold@canonical.com>
*/
#include "helpers.h"
#include "in_process_server.h"
#include "layer_shell_v1.h"
#include "xdg_shell_stable.h"
#include "version_specifier.h"
#include "geometry/rectangle.h"
#include <gmock/gmock.h>
using namespace testing;
using wlcs::X, wlcs::Y, wlcs::DeltaX, wlcs::DeltaY, wlcs::Width, wlcs::Height, wlcs::Size, wlcs::Point, wlcs::Rectangle;
namespace
{
class LayerSurfaceTest: public wlcs::StartedInProcessServer
{
public:
LayerSurfaceTest()
: client{the_server()},
surface{client},
layer_surface{client, surface}
{
}
void commit_and_wait_for_configure()
{
wl_surface_commit(surface);
layer_surface.dispatch_until_configure();
}
void expect_surface_is_at_position(Point pos, wlcs::Surface const& expected)
{
auto pointer = the_server().create_pointer();
pointer.move_to(pos.x.as_int() + 10, pos.y.as_int() + 10);
client.roundtrip();
EXPECT_THAT(client.window_under_cursor(), Eq((wl_surface*)expected));
if (client.window_under_cursor() == expected)
{
EXPECT_THAT(wl_fixed_to_int(client.pointer_position().first), Eq(10));
EXPECT_THAT(wl_fixed_to_int(client.pointer_position().second), Eq(10));
}
}
void expect_surface_is_at_position(Point pos)
{
expect_surface_is_at_position(pos, surface);
}
auto output_rect() const -> Rectangle
{
EXPECT_THAT(client.output_count(), Ge(1u)) << "There are no outputs to get a size from";
EXPECT_THAT(client.output_count(), Eq(1u)) << "Unclear which output the layer shell surface will be placed on";
auto output_state = client.output_state(0);
EXPECT_THAT((bool)output_state.mode_size, Eq(true)) << "Output has no size";
EXPECT_THAT((bool)output_state.geometry_position, Eq(true)) << "Output has no position";
auto size = output_state.mode_size.value();
if (output_state.scale)
{
size.first /= output_state.scale.value();
size.second /= output_state.scale.value();
}
auto pos = output_state.geometry_position.value();
return {{pos.first, pos.second}, {size.first, size.second}};
}
wlcs::Client client;
wlcs::Surface surface;
wlcs::LayerSurfaceV1 layer_surface;
Size static constexpr default_size{200, 300};
int static const default_width = default_size.width.as_int();
int static const default_height = default_size.height.as_int();
};
struct LayerSurfaceLayout
{
struct Anchor
{
bool left, right, top, bottom;
};
struct Margin
{
DeltaX left, right;
DeltaY top, bottom;
};
static auto get_all() -> std::vector<LayerSurfaceLayout>
{
bool const range[]{false, true};
std::vector<LayerSurfaceLayout> result;
for (auto left: range)
{
for (auto right: range)
{
for (auto top: range)
{
for (auto bottom: range)
{
result.emplace_back(Anchor{left, right, top, bottom});
result.emplace_back(
Anchor{left, right, top, bottom},
Margin{DeltaX{6}, DeltaX{9}, DeltaY{12}, DeltaY{15}});
}
}
}
}
return result;
}
LayerSurfaceLayout(Anchor anchor)
: anchor{anchor},
margin{{}, {}, {}, {}}
{
}
LayerSurfaceLayout(Anchor anchor, Margin margin)
: anchor{anchor},
margin{margin}
{
}
operator uint32_t() const
{
return
(anchor.left ? ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT : 0) |
(anchor.right ? ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT : 0) |
(anchor.top ? ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP : 0) |
(anchor.bottom ? ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM : 0);
}
auto h_expand() const -> bool { return anchor.left && anchor.right; }
auto v_expand() const -> bool { return anchor.top && anchor.bottom; }
auto placement_rect(Rectangle const& output) const -> Rectangle
{
auto const config_size = configure_size(output);
auto const width = config_size.width.as_int() ? config_size.width : LayerSurfaceTest::default_size.width;
auto const height = config_size.height.as_int() ? config_size.height : LayerSurfaceTest::default_size.height;
X const x =
(anchor.left ?
output.top_left.x + margin.left :
(anchor.right ?
(output.top_left.x + as_delta(output.size.width) - as_delta(width) - margin.right) :
(output.top_left.x + (output.size.width - width) / 2)
)
);
Y const y =
(anchor.top ?
output.top_left.y + margin.top :
(anchor.bottom ?
(output.top_left.y + as_delta(output.size.height) - as_delta(height) - margin.bottom) :
(output.top_left.y + (output.size.height - height) / 2)
)
);
return {{x, y}, {width, height}};
}
auto request_size() const -> Size
{
return {
h_expand() ? Width{} : LayerSurfaceTest::default_size.width,
v_expand() ? Height{} : LayerSurfaceTest::default_size.height};
}
auto configure_size(Rectangle const& output) const -> Size
{
auto const configure_width = h_expand() ? output.size.width - margin.left - margin.right : Width{};
auto const configure_height = v_expand() ? output.size.height - margin.top - margin.bottom : Height{};
return {configure_width, configure_height};
}
// Will always either return 0, or a single enum value
auto attached_edge() const -> zwlr_layer_surface_v1_anchor
{
if (anchor.top == anchor.bottom)
{
if (anchor.left && !anchor.right)
return ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT;
else if (anchor.right && !anchor.left)
return ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;
}
else if (anchor.left == anchor.right)
{
if (anchor.top && !anchor.bottom)
return ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP;
else if (anchor.bottom && !anchor.top)
return ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM;
}
return (zwlr_layer_surface_v1_anchor)0;
}
Anchor const anchor;
Margin const margin;
};
static void invoke_zwlr_layer_surface_v1_set_margin(
zwlr_layer_surface_v1* layer_surface,
LayerSurfaceLayout::Margin const& margin)
{
zwlr_layer_surface_v1_set_margin(
layer_surface,
margin.top.as_int(),
margin.right.as_int(),
margin.bottom.as_int(),
margin.left.as_int());
}
std::ostream& operator<<(std::ostream& os, const LayerSurfaceLayout& layout)
{
std::vector<std::string> strs;
if (layout.anchor.left)
strs.emplace_back("left");
if (layout.anchor.right)
strs.emplace_back("right");
if (layout.anchor.top)
strs.emplace_back("top");
if (layout.anchor.bottom)
strs.emplace_back("bottom");
if (strs.empty())
strs.emplace_back("none");
os << "Anchor{";
for (auto i = 0u; i < strs.size(); i++)
{
if (i > 0)
os << " | ";
os << strs[i];
}
os << "}, Margin{";
if (layout.margin.left.as_int() ||
layout.margin.right.as_int() ||
layout.margin.top.as_int() ||
layout.margin.bottom.as_int())
{
os << "l: " << layout.margin.left << ", ";
os << "r: " << layout.margin.right << ", ";
os << "t: " << layout.margin.top << ", ";
os << "b: " << layout.margin.bottom;
}
os << "}";
return os;
}
class LayerSurfaceLayoutTest:
public LayerSurfaceTest,
public testing::WithParamInterface<LayerSurfaceLayout>
{
};
struct LayerLayerParams
{
std::optional<zwlr_layer_shell_v1_layer> below;
std::optional<zwlr_layer_shell_v1_layer> above;
};
std::ostream& operator<<(std::ostream& os, const LayerLayerParams& layer)
{
os << "layers:";
for (auto const& i : {layer.below, layer.above})
{
os << " ";
os << "Layer{";
if (i)
{
switch (auto const value = i.value())
{
case ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND:
os << "background";
break;
case ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM:
os << "bottom";
break;
case ZWLR_LAYER_SHELL_V1_LAYER_TOP:
os << "top";
break;
case ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY:
os << "overlay";
break;
default:
os << "INVALID(" << value << ")";
}
}
else
{
os << "none";
}
os << "}";
}
return os;
}
class LayerSurfaceLayerTest:
public wlcs::StartedInProcessServer,
public testing::WithParamInterface<LayerLayerParams>
{
public:
struct SurfaceOnLayer
{
SurfaceOnLayer(
wlcs::Client& client,
std::optional<zwlr_layer_shell_v1_layer> layer)
: surface{layer ? wlcs::Surface{client} : client.create_visible_surface(width, height)}
{
if (layer)
{
layer_surface.emplace(client, surface, layer.value());
zwlr_layer_surface_v1_set_size(layer_surface.value(), width, height);
surface.attach_visible_buffer(width, height);
}
}
wlcs::Surface surface;
std::optional<wlcs::LayerSurfaceV1> layer_surface;
};
LayerSurfaceLayerTest()
: client(the_server())
{
}
static const int width{200}, height{100};
wlcs::Client client;
};
struct SizeAndAnchors
{
uint32_t width, height;
LayerSurfaceLayout anchors;
};
class LayerSurfaceErrorsTest:
public LayerSurfaceTest,
public testing::WithParamInterface<SizeAndAnchors>
{
};
}
TEST_F(LayerSurfaceTest, specifying_no_size_without_anchors_is_an_error)
{
try
{
// Protocol specifies that a size of (0,0) is the default
commit_and_wait_for_configure();
}
catch (wlcs::ProtocolError const& err)
{
EXPECT_THAT(err.interface(), Eq(&zwlr_layer_surface_v1_interface));
// The protocol does not explicitly state what error to send here; INVALID_SIZE seems most appropriate
EXPECT_THAT(err.error_code(), Eq(ZWLR_LAYER_SURFACE_V1_ERROR_INVALID_SIZE));
return;
}
FAIL() << "Expected protocol error not raised";
}
TEST_F(LayerSurfaceTest, specifying_zero_size_without_anchors_is_an_error)
{
try
{
zwlr_layer_surface_v1_set_size(layer_surface, 0, 0);
commit_and_wait_for_configure();
}
catch (wlcs::ProtocolError const& err)
{
EXPECT_THAT(err.interface(), Eq(&zwlr_layer_surface_v1_interface));
// The protocol does not explicitly state what error to send here; INVALID_SIZE seems most appropriate
EXPECT_THAT(err.error_code(), Eq(ZWLR_LAYER_SURFACE_V1_ERROR_INVALID_SIZE));
return;
}
FAIL() << "Expected protocol error not raised";
}
TEST_P(LayerSurfaceErrorsTest, specifying_zero_size_without_corresponding_anchors_is_an_error)
{
try
{
zwlr_layer_surface_v1_set_size(layer_surface, GetParam().width, GetParam().height);
zwlr_layer_surface_v1_set_anchor(layer_surface, GetParam().anchors);
commit_and_wait_for_configure();
}
catch (wlcs::ProtocolError const& err)
{
EXPECT_THAT(err.interface(), Eq(&zwlr_layer_surface_v1_interface));
// The protocol does not explicitly state what error to send here; INVALID_SIZE seems most appropriate
EXPECT_THAT(err.error_code(), Eq(ZWLR_LAYER_SURFACE_V1_ERROR_INVALID_SIZE));
return;
}
FAIL() << "Expected protocol error not raised";
}
INSTANTIATE_TEST_SUITE_P(
Anchors,
LayerSurfaceErrorsTest,
testing::Values(
SizeAndAnchors{0, 0, LayerSurfaceLayout{{false, false, false, false}}},
SizeAndAnchors{0, 0, LayerSurfaceLayout{{true, false, false, false}}},
SizeAndAnchors{0, 0, LayerSurfaceLayout{{false, true, false, false}}},
SizeAndAnchors{0, 0, LayerSurfaceLayout{{true, true, false, false}}},
SizeAndAnchors{0, 0, LayerSurfaceLayout{{false, false, true, false}}},
SizeAndAnchors{0, 0, LayerSurfaceLayout{{true, false, true, false}}},
SizeAndAnchors{0, 0, LayerSurfaceLayout{{false, true, true, false}}},
SizeAndAnchors{0, 0, LayerSurfaceLayout{{true, true, true, false}}},
SizeAndAnchors{0, 0, LayerSurfaceLayout{{false, false, false, true}}},
SizeAndAnchors{0, 0, LayerSurfaceLayout{{true, false, false, true}}},
SizeAndAnchors{0, 0, LayerSurfaceLayout{{false, true, false, true}}},
SizeAndAnchors{0, 0, LayerSurfaceLayout{{true, true, false, true}}},
SizeAndAnchors{0, 0, LayerSurfaceLayout{{false, true, true, true}}},
SizeAndAnchors{200, 0, LayerSurfaceLayout{{false, false, false, true}}},
SizeAndAnchors{200, 0, LayerSurfaceLayout{{false, false, true, false}}},
SizeAndAnchors{0, 200, LayerSurfaceLayout{{true, false, false, true}}},
SizeAndAnchors{0, 200, LayerSurfaceLayout{{false, true, true, false}}}
));
TEST_F(LayerSurfaceTest, can_open_layer_surface)
{
zwlr_layer_surface_v1_set_size(layer_surface, default_width, default_height);
commit_and_wait_for_configure();
}
TEST_F(LayerSurfaceTest, gets_configured_with_supplied_size_when_set)
{
int width = 123, height = 546;
zwlr_layer_surface_v1_set_size(layer_surface, width, height);
commit_and_wait_for_configure();
EXPECT_THAT(layer_surface.last_size(), Eq(Size{width, height}));
}
TEST_F(LayerSurfaceTest, gets_configured_with_supplied_size_even_when_anchored_to_edges)
{
int width = 321, height = 218;
zwlr_layer_surface_v1_set_anchor(layer_surface, LayerSurfaceLayout({true, true, true, true}));
zwlr_layer_surface_v1_set_size(layer_surface, width, height);
commit_and_wait_for_configure();
EXPECT_THAT(layer_surface.last_size(), Eq(Size{width, height}));
}
TEST_F(LayerSurfaceTest, when_anchored_to_all_edges_gets_configured_with_output_size)
{
zwlr_layer_surface_v1_set_anchor(layer_surface, LayerSurfaceLayout({true, true, true, true}));
commit_and_wait_for_configure();
ASSERT_THAT(layer_surface.last_size(), Eq(output_rect().size));
}
TEST_F(LayerSurfaceTest, gets_configured_after_anchor_change)
{
zwlr_layer_surface_v1_set_size(layer_surface, default_width, default_height);
commit_and_wait_for_configure();
zwlr_layer_surface_v1_set_size(layer_surface, 0, 0);
zwlr_layer_surface_v1_set_anchor(layer_surface, LayerSurfaceLayout({true, true, true, true}));
commit_and_wait_for_configure();
EXPECT_THAT(layer_surface.last_size().width, Gt(Width{}));
EXPECT_THAT(layer_surface.last_size().height, Gt(Height{}));
}
TEST_F(LayerSurfaceTest, destroy_request_supported)
{
wlcs::Client client{the_server()};
{
auto const layer_shell = client.bind_if_supported<zwlr_layer_shell_v1>(
wlcs::AtLeastVersion{ZWLR_LAYER_SHELL_V1_DESTROY_SINCE_VERSION});
client.roundtrip();
}
// layer_shell is now destroyed
client.roundtrip();
}
TEST_F(LayerSurfaceTest, destroy_request_not_sent_when_not_supported)
{
wlcs::Client client{the_server()};
{
auto const layer_shell = client.bind_if_supported<zwlr_layer_shell_v1>(
wlcs::ExactlyVersion{ZWLR_LAYER_SHELL_V1_DESTROY_SINCE_VERSION - 1});
client.roundtrip();
}
// layer_shell is now destroyed
client.roundtrip();
}
TEST_F(LayerSurfaceTest, does_not_take_keyboard_focus_without_keyboard_interactivity)
{
auto normal_surface = client.create_visible_surface(100, 100);
the_server().move_surface_to(normal_surface, 0, default_height);
auto pointer = the_server().create_pointer();
pointer.move_to(5, default_height + 5);
pointer.left_click();
client.roundtrip();
ASSERT_THAT(client.keyboard_focused_window(), Eq(static_cast<wl_surface*>(normal_surface)))
<< "Could not run test because normal surface was not given keyboeard focus";
zwlr_layer_surface_v1_set_size(layer_surface, default_width, default_height);
zwlr_layer_surface_v1_set_anchor(
layer_surface,
ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT);
commit_and_wait_for_configure();
surface.attach_visible_buffer(default_width, default_height);
EXPECT_THAT(client.keyboard_focused_window(), Eq(static_cast<wl_surface*>(normal_surface)))
<< "Normal surface lost keyboard focus when non-keyboard layer surface appeared";
}
TEST_F(LayerSurfaceTest, takes_keyboard_focus_with_exclusive_keyboard_interactivity)
{
auto normal_surface = client.create_visible_surface(100, 100);
the_server().move_surface_to(normal_surface, 0, default_height);
auto pointer = the_server().create_pointer();
pointer.move_to(5, default_height + 5);
pointer.left_click();
client.roundtrip();
ASSERT_THAT(client.keyboard_focused_window(), Eq(static_cast<wl_surface*>(normal_surface)))
<< "Could not run test because normal surface was not given keyboeard focus";
zwlr_layer_surface_v1_set_keyboard_interactivity(
layer_surface,
ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE);
zwlr_layer_surface_v1_set_size(layer_surface, default_width, default_height);
zwlr_layer_surface_v1_set_anchor(
layer_surface,
ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT);
commit_and_wait_for_configure();
surface.attach_visible_buffer(default_width, default_height);
EXPECT_THAT(client.keyboard_focused_window(), Eq(static_cast<wl_surface*>(surface)))
<< "Layer surface not given keyboard focus in exclusive mode";
}
TEST_F(LayerSurfaceTest, takes_keyboard_focus_after_click_with_on_demand_keyboard_interactivity)
{
auto normal_surface = client.create_visible_surface(100, 100);
the_server().move_surface_to(normal_surface, 0, default_height);
auto pointer = the_server().create_pointer();
pointer.move_to(5, default_height + 5);
pointer.left_click();
client.roundtrip();
ASSERT_THAT(client.keyboard_focused_window(), Eq(static_cast<wl_surface*>(normal_surface)))
<< "Could not run test because normal surface was not given keyboeard focus";
{
wlcs::Client client{the_server()};
auto const layer_shell = client.bind_if_supported<zwlr_layer_shell_v1>(
wlcs::AtLeastVersion{ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_ON_DEMAND_SINCE_VERSION});
client.roundtrip();
}
zwlr_layer_surface_v1_set_keyboard_interactivity(
layer_surface,
ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_ON_DEMAND);
zwlr_layer_surface_v1_set_size(layer_surface, default_width, default_height);
zwlr_layer_surface_v1_set_anchor(
layer_surface,
ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT);
commit_and_wait_for_configure();
surface.attach_visible_buffer(default_width, default_height);
client.roundtrip();
pointer.move_to(5, 5);
pointer.left_click();
client.roundtrip();
EXPECT_THAT(client.keyboard_focused_window(), Eq(static_cast<wl_surface*>(surface)))
<< "Layer surface not given keyboard focus in on-demand mode";
}
TEST_F(LayerSurfaceTest, does_not_lose_keyboard_focus_with_exclusive_keyboard_interactivity)
{
zwlr_layer_surface_v1_set_keyboard_interactivity(
layer_surface,
ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE);
zwlr_layer_surface_v1_set_size(layer_surface, default_width, default_height);
zwlr_layer_surface_v1_set_anchor(
layer_surface,
ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT);
commit_and_wait_for_configure();
surface.attach_visible_buffer(default_width, default_height);
ASSERT_THAT(client.keyboard_focused_window(), Eq(static_cast<wl_surface*>(surface)))
<< "Layer surface not given keyboard focus in exclusive mode";
auto normal_surface = client.create_visible_surface(100, 100);
the_server().move_surface_to(normal_surface, 0, default_height);
auto pointer = the_server().create_pointer();
pointer.move_to(5, default_height + 5);
pointer.left_click();
client.roundtrip();
ASSERT_THAT(client.keyboard_focused_window(), Eq(static_cast<wl_surface*>(surface)))
<< "Creating a normal surface caused the layer surface in exclusive mode to lose keyboard focus";
}
TEST_F(LayerSurfaceTest, can_lose_keyboard_focus_with_on_demand_keyboard_interactivity)
{
{
wlcs::Client client{the_server()};
auto const layer_shell = client.bind_if_supported<zwlr_layer_shell_v1>(
wlcs::AtLeastVersion{ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_ON_DEMAND_SINCE_VERSION});
client.roundtrip();
}
zwlr_layer_surface_v1_set_keyboard_interactivity(
layer_surface,
ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_ON_DEMAND);
zwlr_layer_surface_v1_set_size(layer_surface, default_width, default_height);
zwlr_layer_surface_v1_set_anchor(
layer_surface,
ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT);
commit_and_wait_for_configure();
surface.attach_visible_buffer(default_width, default_height);
ASSERT_THAT(client.keyboard_focused_window(), Eq(static_cast<wl_surface*>(surface)))
<< "Layer surface not given keyboard focus in exclusive mode";
auto normal_surface = client.create_visible_surface(100, 100);
the_server().move_surface_to(normal_surface, 0, default_height);
auto pointer = the_server().create_pointer();
pointer.move_to(5, default_height + 5);
pointer.left_click();
client.roundtrip();
ASSERT_THAT(client.keyboard_focused_window(), Ne(static_cast<wl_surface*>(surface)))
<< "Creating a normal surface did not cause the layer surface in on-demand mode to lose keyboard focus";
}
TEST_F(LayerSurfaceTest, takes_keyboard_focus_when_interactivity_changes_to_exclusive)
{
auto normal_surface = client.create_visible_surface(100, 100);
the_server().move_surface_to(normal_surface, 0, default_height);
auto pointer = the_server().create_pointer();
pointer.move_to(5, default_height + 5);
pointer.left_click();
client.roundtrip();
ASSERT_THAT(client.keyboard_focused_window(), Eq(static_cast<wl_surface*>(normal_surface)))
<< "Could not run test because normal surface was not given keyboeard focus";
zwlr_layer_surface_v1_set_keyboard_interactivity(
layer_surface,
ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE);
zwlr_layer_surface_v1_set_size(layer_surface, default_width, default_height);
zwlr_layer_surface_v1_set_anchor(
layer_surface,
ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT);
commit_and_wait_for_configure();
surface.attach_visible_buffer(default_width, default_height);
ASSERT_THAT(client.keyboard_focused_window(), Eq(static_cast<wl_surface*>(normal_surface)))
<< "Could not run test because normal surface was not given keyboeard focus";
zwlr_layer_surface_v1_set_keyboard_interactivity(
layer_surface,
ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE);
wl_surface_commit(surface);
client.roundtrip();
ASSERT_THAT(client.keyboard_focused_window(), Eq(static_cast<wl_surface*>(surface)))
<< "Layer surface did not take keyboard focus when mode changed to exclusive";
pointer.left_click();
client.roundtrip();
EXPECT_THAT(client.keyboard_focused_window(), Eq(static_cast<wl_surface*>(surface)))
<< "Layer surface did not hold exclusive keyboard focus";
}
TEST_F(LayerSurfaceTest, loses_keybaord_focus_when_interactivity_changes_to_none)
{
auto normal_surface = client.create_visible_surface(100, 100);
ASSERT_THAT(client.keyboard_focused_window(), Eq(static_cast<wl_surface*>(normal_surface)))
<< "Could not run test because normal surface was not given keyboeard focus";
zwlr_layer_surface_v1_set_keyboard_interactivity(
layer_surface,
ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE);
zwlr_layer_surface_v1_set_size(layer_surface, default_width, default_height);
zwlr_layer_surface_v1_set_anchor(
layer_surface,
ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT);
commit_and_wait_for_configure();
surface.attach_visible_buffer(default_width, default_height);
ASSERT_THAT(client.keyboard_focused_window(), Eq(static_cast<wl_surface*>(surface)))
<< "Could not run test because surface not given keyboard focus despite exclusive interactivity";
zwlr_layer_surface_v1_set_keyboard_interactivity(
layer_surface,
ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE);
wl_surface_commit(surface);
client.roundtrip();
ASSERT_THAT(client.keyboard_focused_window(), Ne(static_cast<wl_surface*>(surface)))
<< "Layer surface did not lose keyboard focus when interactivity changed to none";
}
TEST_P(LayerSurfaceLayoutTest, is_initially_positioned_correctly_for_anchor)
{
auto const layout = GetParam();
auto const output = output_rect();
auto const rect = layout.placement_rect(output);
auto const request_size = layout.request_size();
zwlr_layer_surface_v1_set_anchor(layer_surface, layout);
invoke_zwlr_layer_surface_v1_set_margin(layer_surface, layout.margin);
zwlr_layer_surface_v1_set_size(layer_surface, request_size.width.as_int(), request_size.height.as_int());
commit_and_wait_for_configure();
auto configure_size = layout.configure_size(output);
if (configure_size.width.as_int())
{
EXPECT_THAT(layer_surface.last_size().width, Eq(configure_size.width));
}
if (configure_size.height.as_int())
{
EXPECT_THAT(layer_surface.last_size().height, Eq(configure_size.height));
}
surface.attach_visible_buffer(rect.size.width.as_int(), rect.size.height.as_int());
expect_surface_is_at_position(rect.top_left);
}
TEST_P(LayerSurfaceLayoutTest, is_positioned_correctly_when_explicit_size_does_not_match_buffer_size)
{
auto const layout = GetParam();
int const initial_width{52}, initial_height{74};
auto const rect = layout.placement_rect(output_rect());
auto const request_size = layout.request_size();
zwlr_layer_surface_v1_set_anchor(layer_surface, layout);
invoke_zwlr_layer_surface_v1_set_margin(layer_surface, layout.margin);
zwlr_layer_surface_v1_set_size(layer_surface, request_size.width.as_int(), request_size.height.as_int());
commit_and_wait_for_configure();
surface.attach_visible_buffer(initial_width, initial_height);
expect_surface_is_at_position(rect.top_left);
}
TEST_P(LayerSurfaceLayoutTest, is_positioned_correctly_when_layout_changed)
{
auto const layout = GetParam();
auto const output = output_rect();
auto const result_rect = layout.placement_rect(output);
auto const request_size = layout.request_size();
zwlr_layer_surface_v1_set_size(layer_surface, default_width - 5, default_height - 2);
commit_and_wait_for_configure();
zwlr_layer_surface_v1_set_anchor(layer_surface, layout);
invoke_zwlr_layer_surface_v1_set_margin(layer_surface, layout.margin);
zwlr_layer_surface_v1_set_size(layer_surface, request_size.width.as_int(), request_size.height.as_int());
surface.attach_visible_buffer(result_rect.size.width.as_int(), result_rect.size.height.as_int());
wl_surface_commit(surface);
client.roundtrip(); // Sometimes we get a configure, sometimes we don't
auto configure_size = layout.configure_size(output);
if (configure_size.width.as_int())
{
EXPECT_THAT(layer_surface.last_size().width, Eq(configure_size.width));
}
if (configure_size.height.as_int())
{
EXPECT_THAT(layer_surface.last_size().height, Eq(configure_size.height));
}
expect_surface_is_at_position(result_rect.top_left);
}
TEST_P(LayerSurfaceLayoutTest, is_positioned_correctly_after_multiple_changes)
{
auto const layout = GetParam();
auto const output = output_rect();
auto const result_rect = layout.placement_rect(output);
auto const request_size = layout.request_size();
zwlr_layer_surface_v1_set_size(layer_surface, default_width, default_height);
commit_and_wait_for_configure();
zwlr_layer_surface_v1_set_anchor(layer_surface, layout);
invoke_zwlr_layer_surface_v1_set_margin(layer_surface, layout.margin);
zwlr_layer_surface_v1_set_size(layer_surface, request_size.width.as_int(), request_size.height.as_int());
surface.attach_visible_buffer(result_rect.size.width.as_int(), result_rect.size.height.as_int());
wl_surface_commit(surface);
client.roundtrip(); // Sometimes we get a configure, sometimes we don't
zwlr_layer_surface_v1_set_anchor(layer_surface, 0);
zwlr_layer_surface_v1_set_margin(layer_surface, 0, 0, 0, 0);
zwlr_layer_surface_v1_set_size(layer_surface, default_width, default_height);
surface.attach_visible_buffer(default_width, default_height);
wl_surface_commit(surface);
client.roundtrip(); // Sometimes we get a configure, sometimes we don't
EXPECT_THAT(layer_surface.last_size().width, Eq(default_size.width));
EXPECT_THAT(layer_surface.last_size().height, Eq(default_size.height));
auto const expected_top_left = output.top_left + as_displacement(
as_size(as_displacement(output.size) - as_displacement(default_size)) / 2);
expect_surface_is_at_position(expected_top_left);
}
TEST_P(LayerSurfaceLayoutTest, is_positioned_to_accommodate_other_surfaces_exclusive_zone)
{
auto const layout = GetParam();
auto const request_size = layout.request_size();
auto const initial_rect = layout.placement_rect(output_rect());
auto const exclusive = 12;
zwlr_layer_surface_v1_set_anchor(layer_surface, layout);
invoke_zwlr_layer_surface_v1_set_margin(layer_surface, layout.margin);
zwlr_layer_surface_v1_set_size(layer_surface, request_size.width.as_int(), request_size.height.as_int());
commit_and_wait_for_configure();
surface.attach_visible_buffer(initial_rect.size.width.as_int(), initial_rect.size.height.as_int());
// Create layer surfaces with exclusive zones on the top and left of the output to push our surface out of the way
wlcs::Surface top_surface{client};
wlcs::LayerSurfaceV1 top_layer_surface{client, top_surface};
zwlr_layer_surface_v1_set_anchor(top_layer_surface, ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP);
zwlr_layer_surface_v1_set_exclusive_zone(top_layer_surface, exclusive);
zwlr_layer_surface_v1_set_size(top_layer_surface, exclusive, exclusive);
wl_surface_commit(top_surface);
top_layer_surface.dispatch_until_configure();
top_surface.attach_visible_buffer(exclusive, exclusive);
wl_surface_commit(top_surface);
wlcs::Surface left_surface{client};
wlcs::LayerSurfaceV1 left_layer_surface{client, left_surface};
zwlr_layer_surface_v1_set_anchor(left_layer_surface, ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT);
zwlr_layer_surface_v1_set_exclusive_zone(left_layer_surface, exclusive);
zwlr_layer_surface_v1_set_size(left_layer_surface, exclusive, exclusive);
wl_surface_commit(left_surface);
left_layer_surface.dispatch_until_configure();
left_surface.attach_visible_buffer(exclusive, exclusive);
wl_surface_commit(left_surface);
client.roundtrip();
auto non_exlusive_zone = output_rect();
non_exlusive_zone.top_left.x += DeltaX{exclusive};
non_exlusive_zone.top_left.y += DeltaY{exclusive};
non_exlusive_zone.size.width -= DeltaX{exclusive};
non_exlusive_zone.size.height -= DeltaY{exclusive};
auto expected_config_size = layout.configure_size(non_exlusive_zone);
if (expected_config_size.width.as_int())
{
EXPECT_THAT(layer_surface.last_size().width, Eq(expected_config_size.width));
}
if (expected_config_size.height.as_int())
{
EXPECT_THAT(layer_surface.last_size().height, Eq(expected_config_size.height));
}
auto const expected_placement = layout.placement_rect(non_exlusive_zone);
surface.attach_visible_buffer(expected_placement.size.width.as_int(), expected_placement.size.height.as_int());
expect_surface_is_at_position(expected_placement.top_left);
}
TEST_P(LayerSurfaceLayoutTest, maximized_xdg_toplevel_is_shrunk_for_exclusive_zone)
{
int const exclusive_zone = 25;
int width = 0, height = 0;
wlcs::Surface other_surface{client};
wlcs::XdgSurfaceStable xdg_surface{client, other_surface};
wlcs::XdgToplevelStable toplevel{xdg_surface};
ON_CALL(toplevel, configure).WillByDefault([&](auto w, auto h, wl_array* /*states*/)
{
if (w != width || h != height)
{
width = w;
height = h;
if (w == 0)
w = 100;
if (h == 0)
h = 150;
other_surface.attach_buffer(w, h);
xdg_surface_set_window_geometry(xdg_surface, 0, 0, w, h);
}
});
ON_CALL(xdg_surface, configure).WillByDefault([&](uint32_t serial)
{
xdg_surface_ack_configure(xdg_surface, serial);
wl_surface_commit(other_surface);
});
xdg_toplevel_set_maximized(toplevel);
wl_surface_commit(other_surface);
client.dispatch_until([&](){ return width > 0; });
int const initial_width = width;
int const initial_height = height;
ASSERT_THAT(initial_width, Gt(0)) << "Can't test as shell did not configure XDG surface with a size";
ASSERT_THAT(initial_height, Gt(0)) << "Can't test as shell did not configure XDG surface with a size";
auto const layout = GetParam();
auto const request_size = layout.request_size();
auto const rect = layout.placement_rect(output_rect());
zwlr_layer_surface_v1_set_anchor(layer_surface, layout);
invoke_zwlr_layer_surface_v1_set_margin(layer_surface, layout.margin);
zwlr_layer_surface_v1_set_exclusive_zone(layer_surface, exclusive_zone);
zwlr_layer_surface_v1_set_size(layer_surface, request_size.width.as_int(), request_size.height.as_int());
commit_and_wait_for_configure();
surface.attach_visible_buffer(rect.size.width.as_int(), rect.size.height.as_int());
int const new_width = width;
int const new_height = height;
int expected_width = initial_width;
int expected_height = initial_height;
switch (layout.attached_edge())
{
case ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT:
expected_width -= exclusive_zone + layout.margin.left.as_int();
break;
case ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT:
expected_width -= exclusive_zone + layout.margin.right.as_int();
break;
case ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP:
expected_height -= exclusive_zone + layout.margin.top.as_int();
break;
case ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM:
expected_height -= exclusive_zone + layout.margin.bottom.as_int();
break;
default: ;
}
EXPECT_THAT(new_width, Eq(expected_width));
EXPECT_THAT(new_height, Eq(expected_height));
}
TEST_P(LayerSurfaceLayoutTest, surfaces_with_exclusive_zone_set_to_negative_one_do_not_respect_other_exclusive_zones)
{
auto const layout = GetParam();
auto const request_size = layout.request_size();
auto const initial_rect = layout.placement_rect(output_rect());
zwlr_layer_surface_v1_set_anchor(layer_surface, layout);
zwlr_layer_surface_v1_set_exclusive_zone(layer_surface, -1);
invoke_zwlr_layer_surface_v1_set_margin(layer_surface, layout.margin);
zwlr_layer_surface_v1_set_size(layer_surface, request_size.width.as_int(), request_size.height.as_int());
commit_and_wait_for_configure();
surface.attach_visible_buffer(initial_rect.size.width.as_int(), initial_rect.size.height.as_int());
// First, let's check that the surface is in the position that we expect and
// that the compositor has suggested the size that we expect.
client.roundtrip();
EXPECT_THAT(layer_surface.last_size().width, Eq(initial_rect.size.width));
EXPECT_THAT(layer_surface.last_size().height, Eq(initial_rect.size.height));
expect_surface_is_at_position(initial_rect.top_left);
// Next, create layer surfaces with exclusive zones on the top and left of the output that would
// theoretically push our surface out of the way if it did NOT have an exclusive zone of -1.
auto const exclusive = 12;
wlcs::Surface top_surface{client};
wlcs::LayerSurfaceV1 top_layer_surface{client, top_surface};
zwlr_layer_surface_v1_set_anchor(top_layer_surface, ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP);
zwlr_layer_surface_v1_set_exclusive_zone(top_layer_surface, exclusive);
zwlr_layer_surface_v1_set_size(top_layer_surface, exclusive, exclusive);
wl_surface_commit(top_surface);
top_layer_surface.dispatch_until_configure();
top_surface.attach_visible_buffer(exclusive, exclusive);
wl_surface_commit(top_surface);
wlcs::Surface left_surface{client};
wlcs::LayerSurfaceV1 left_layer_surface{client, left_surface};
zwlr_layer_surface_v1_set_anchor(left_layer_surface, ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT);
zwlr_layer_surface_v1_set_exclusive_zone(left_layer_surface, exclusive);
zwlr_layer_surface_v1_set_size(left_layer_surface, exclusive, exclusive);
wl_surface_commit(left_surface);
left_layer_surface.dispatch_until_configure();
left_surface.attach_visible_buffer(exclusive, exclusive);
wl_surface_commit(left_surface);
// Finally, let's recheck that we aren't receiving a different suggested size
// and that we are in the right spot.
client.roundtrip();
EXPECT_THAT(layer_surface.last_size().width, Eq(initial_rect.size.width));
EXPECT_THAT(layer_surface.last_size().height, Eq(initial_rect.size.height));
expect_surface_is_at_position(initial_rect.top_left);
}
TEST_P(LayerSurfaceLayoutTest, simple_popup_positioned_correctly)
{
auto const layout = GetParam();
auto const output = output_rect();
auto const layer_surface_rect = layout.placement_rect(output);
auto const layer_surface_request_size = layout.request_size();
zwlr_layer_surface_v1_set_anchor(layer_surface, layout);
invoke_zwlr_layer_surface_v1_set_margin(layer_surface, layout.margin);
zwlr_layer_surface_v1_set_size(layer_surface, layer_surface_request_size.width.as_int(), layer_surface_request_size.height.as_int());
commit_and_wait_for_configure();
surface.attach_visible_buffer(layer_surface_rect.size.width.as_int(), layer_surface_rect.size.height.as_int());
auto const popup_size = std::make_pair(30, 30);
wlcs::XdgPositionerStable positioner{client};
xdg_positioner_set_size(positioner, popup_size.first, popup_size.second);
xdg_positioner_set_anchor_rect(
positioner,
5, 5,
layer_surface_rect.size.width.as_int() - 10, layer_surface_rect.size.height.as_int() - 10);
xdg_positioner_set_anchor(positioner, 0);
xdg_positioner_set_gravity(positioner, XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT);
wlcs::Surface popup_wl_surface{client};
wlcs::XdgSurfaceStable popup_xdg_surface{client, popup_wl_surface};
wlcs::XdgPopupStable popup_xdg_popup{popup_xdg_surface, std::nullopt, positioner};
zwlr_layer_surface_v1_get_popup(layer_surface, popup_xdg_popup);
int popup_surface_configure_count = 0;
Point popup_configured_position;
ON_CALL(popup_xdg_surface, configure).WillByDefault([&](uint32_t serial)
{
xdg_surface_ack_configure(popup_xdg_surface, serial);
popup_surface_configure_count++;
});
ON_CALL(popup_xdg_popup, configure).WillByDefault([&](int32_t x, int32_t y, int32_t, int32_t)
{
popup_configured_position.x = X{x};
popup_configured_position.y = Y{y};
});
popup_wl_surface.attach_visible_buffer(popup_size.first, popup_size.second);
client.dispatch_until([&](){ return popup_surface_configure_count > 0; });
EXPECT_THAT(
popup_configured_position,
Eq(as_point(layer_surface_rect.size / 2)));
expect_surface_is_at_position(
layer_surface_rect.top_left + as_displacement(layer_surface_rect.size / 2),
popup_wl_surface);
}
INSTANTIATE_TEST_SUITE_P(
Anchor,
LayerSurfaceLayoutTest,
testing::ValuesIn(LayerSurfaceLayout::get_all()));
TEST_P(LayerSurfaceLayerTest, surface_on_lower_layer_is_initially_placed_below)
{
auto const& param = GetParam();
SurfaceOnLayer above{client, param.above};
SurfaceOnLayer below{client, param.below};
the_server().move_surface_to(above.surface, 100, 0);
the_server().move_surface_to(below.surface, 0, 0);
auto pointer = the_server().create_pointer();
pointer.move_to(1, 1);
client.roundtrip();
ASSERT_THAT(client.window_under_cursor(), Eq((wl_surface*)below.surface))
<< "Test could not run because below surface was not detected when above surface was not in the way";
the_server().move_surface_to(above.surface, 0, 0);
the_server().move_surface_to(below.surface, 0, 0);
pointer.move_to(2, 3);
client.roundtrip();
ASSERT_THAT(client.window_under_cursor(), Ne((wl_surface*)below.surface))
<< "Wrong wl_surface was on top";
ASSERT_THAT(client.window_under_cursor(), Eq((wl_surface*)above.surface))
<< "Correct surface was not on top";
}
TEST_P(LayerSurfaceLayerTest, below_surface_can_not_be_raised_with_click)
{
auto const& param = GetParam();
SurfaceOnLayer above{client, param.above};
SurfaceOnLayer below{client, param.below};
the_server().move_surface_to(above.surface, width / 2, 0);
the_server().move_surface_to(below.surface, 0, 0);
auto pointer = the_server().create_pointer();
pointer.move_to(1, 1);
client.roundtrip();
ASSERT_THAT(client.window_under_cursor(), Eq((wl_surface*)below.surface))
<< "Test could not run because below surface was not detected and clicked on";
pointer.left_button_down();
client.roundtrip();
pointer.left_button_up();
client.roundtrip();
pointer.move_to(width / 2 + 2, 1);
client.roundtrip();
ASSERT_THAT(client.window_under_cursor(), Ne((wl_surface*)below.surface))
<< "Wrong wl_surface was on top";
ASSERT_THAT(client.window_under_cursor(), Eq((wl_surface*)above.surface))
<< "Correct surface was not on top";
}
TEST_P(LayerSurfaceLayerTest, surface_can_be_moved_to_layer)
{
client.bind_if_supported<zwlr_layer_shell_v1>(wlcs::AtLeastVersion{ZWLR_LAYER_SURFACE_V1_SET_LAYER_SINCE_VERSION});
auto const& param = GetParam();
auto initial_below{param.below}, initial_above{param.above};
if (initial_below)
{
initial_below = ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY;
}
if (initial_above)
{
initial_above = ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND;
}
SurfaceOnLayer above{client, initial_above};
SurfaceOnLayer below{client, initial_below};
client.roundtrip();
if (above.layer_surface)
{
zwlr_layer_surface_v1_set_layer(above.layer_surface.value(), param.above.value());
}
if (below.layer_surface)
{
zwlr_layer_surface_v1_set_layer(below.layer_surface.value(), param.below.value());
}
wl_surface_commit(above.surface);
wl_surface_commit(below.surface);
auto pointer = the_server().create_pointer();
the_server().move_surface_to(above.surface, 0, 0);
the_server().move_surface_to(below.surface, 0, 0);
pointer.move_to(2, 3);
client.roundtrip();
ASSERT_THAT(client.window_under_cursor(), Ne((wl_surface*)below.surface))
<< "Wrong wl_surface was on top";
ASSERT_THAT(client.window_under_cursor(), Eq((wl_surface*)above.surface))
<< "Correct surface was not on top";
}
INSTANTIATE_TEST_SUITE_P(
Layer,
LayerSurfaceLayerTest,
testing::Values(
LayerLayerParams{ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND, ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM},
LayerLayerParams{ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM, ZWLR_LAYER_SHELL_V1_LAYER_TOP},
LayerLayerParams{ZWLR_LAYER_SHELL_V1_LAYER_TOP, ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY},
LayerLayerParams{ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND, ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY},
LayerLayerParams{ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM, ZWLR_LAYER_SHELL_V1_LAYER_TOP},
LayerLayerParams{ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND, std::nullopt},
LayerLayerParams{ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM, std::nullopt},
LayerLayerParams{std::nullopt, ZWLR_LAYER_SHELL_V1_LAYER_TOP},
LayerLayerParams{std::nullopt, ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY}
));
// TODO: test it gets put on a specified output
// TODO: test margin
// TODO: test keyboard interactivity