blob: 99c2a0baf6f8f9c1f204d5fe30235862052ca493 [file] [edit]
/*
* Copyright © 2012 Intel Corporation
* Copyright © 2013 Collabora, Ltd.
* Copyright © 2018 Canonical Ltd.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice (including the
* next paragraph) shall be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "helpers.h"
#include "in_process_server.h"
#include "xdg_shell_stable.h"
#include "xdg_shell_v6.h"
#include "layer_shell_v1.h"
#include "version_specifier.h"
#include <gmock/gmock.h>
#include <optional>
using namespace testing;
int const window_width = 400, window_height = 500;
int const popup_width = 60, popup_height = 40;
namespace
{
uint32_t anchor_stable_to_v6(xdg_positioner_anchor anchor)
{
switch (anchor)
{
case XDG_POSITIONER_ANCHOR_NONE:
return ZXDG_POSITIONER_V6_ANCHOR_NONE;
case XDG_POSITIONER_ANCHOR_TOP:
return ZXDG_POSITIONER_V6_ANCHOR_TOP;
case XDG_POSITIONER_ANCHOR_BOTTOM:
return ZXDG_POSITIONER_V6_ANCHOR_BOTTOM;
case XDG_POSITIONER_ANCHOR_LEFT:
return ZXDG_POSITIONER_V6_ANCHOR_LEFT;
case XDG_POSITIONER_ANCHOR_RIGHT:
return ZXDG_POSITIONER_V6_ANCHOR_RIGHT;
case XDG_POSITIONER_ANCHOR_TOP_LEFT:
return ZXDG_POSITIONER_V6_ANCHOR_TOP | ZXDG_POSITIONER_V6_ANCHOR_LEFT;
case XDG_POSITIONER_ANCHOR_BOTTOM_LEFT:
return ZXDG_POSITIONER_V6_ANCHOR_BOTTOM | ZXDG_POSITIONER_V6_ANCHOR_LEFT;
case XDG_POSITIONER_ANCHOR_TOP_RIGHT:
return ZXDG_POSITIONER_V6_ANCHOR_TOP | ZXDG_POSITIONER_V6_ANCHOR_RIGHT;
case XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT:
return ZXDG_POSITIONER_V6_ANCHOR_BOTTOM | ZXDG_POSITIONER_V6_ANCHOR_RIGHT;
default:
return ZXDG_POSITIONER_V6_ANCHOR_NONE;
}
}
uint32_t gravity_stable_to_v6(xdg_positioner_gravity gravity)
{
switch (gravity)
{
case XDG_POSITIONER_GRAVITY_NONE:
return ZXDG_POSITIONER_V6_GRAVITY_NONE;
case XDG_POSITIONER_GRAVITY_TOP:
return ZXDG_POSITIONER_V6_GRAVITY_TOP;
case XDG_POSITIONER_GRAVITY_BOTTOM:
return ZXDG_POSITIONER_V6_GRAVITY_BOTTOM;
case XDG_POSITIONER_GRAVITY_LEFT:
return ZXDG_POSITIONER_V6_GRAVITY_LEFT;
case XDG_POSITIONER_GRAVITY_RIGHT:
return ZXDG_POSITIONER_V6_GRAVITY_RIGHT;
case XDG_POSITIONER_GRAVITY_TOP_LEFT:
return ZXDG_POSITIONER_V6_GRAVITY_TOP | ZXDG_POSITIONER_V6_GRAVITY_LEFT;
case XDG_POSITIONER_GRAVITY_BOTTOM_LEFT:
return ZXDG_POSITIONER_V6_GRAVITY_BOTTOM | ZXDG_POSITIONER_V6_GRAVITY_LEFT;
case XDG_POSITIONER_GRAVITY_TOP_RIGHT:
return ZXDG_POSITIONER_V6_ANCHOR_TOP | ZXDG_POSITIONER_V6_ANCHOR_RIGHT;
case XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT:
return ZXDG_POSITIONER_V6_GRAVITY_BOTTOM | ZXDG_POSITIONER_V6_GRAVITY_RIGHT;
default:
return ZXDG_POSITIONER_V6_GRAVITY_NONE;
}
}
uint32_t constraint_adjustment_stable_to_v6(xdg_positioner_constraint_adjustment ca)
{
return ca; // the two enums have the same values
}
struct PositionerParams
{
PositionerParams()
: popup_size{popup_width, popup_height},
anchor_rect{{0, 0}, {window_width, window_height}}
{
}
auto with_size(int x, int y) -> PositionerParams& { popup_size = {x, y}; return *this; }
auto with_anchor_rect(int x, int y, int w, int h) -> PositionerParams& { anchor_rect = {{x, y}, {w, h}}; return *this; }
auto with_anchor(xdg_positioner_anchor value) -> PositionerParams& { anchor_stable = {value}; return *this; }
auto with_gravity(xdg_positioner_gravity value) -> PositionerParams& { gravity_stable = {value}; return *this; }
auto with_constraint_adjustment(uint32_t value) -> PositionerParams& { constraint_adjustment_stable = static_cast<xdg_positioner_constraint_adjustment>(value); return *this; }
auto with_offset(int x, int y) -> PositionerParams& { offset = {{x, y}}; return *this; }
auto with_reactive(bool enable = true) -> PositionerParams& { reactive = enable; return *this; }
auto with_grab(bool enable = true) -> PositionerParams& { grab = enable; return *this; }
std::pair<int, int> popup_size; // will default to XdgPopupStableTestBase::popup_(width|height) if nullopt
std::pair<std::pair<int, int>, std::pair<int, int>> anchor_rect; // will default to the full window rect
std::optional<xdg_positioner_anchor> anchor_stable;
std::optional<xdg_positioner_gravity> gravity_stable;
std::optional<xdg_positioner_constraint_adjustment> constraint_adjustment_stable;
std::optional<std::pair<int, int>> offset;
bool reactive{false};
bool grab{false};
};
struct PositionerTestParams
{
PositionerTestParams(std::string name, int expected_x, int expected_y, PositionerParams const& positioner)
: name{name},
expected_positon{expected_x, expected_y},
expected_size{popup_width, popup_height},
positioner{positioner},
parent_position_func{std::nullopt}
{
}
PositionerTestParams(
std::string name,
int expected_x, int expected_y,
int expected_width, int expected_height,
PositionerParams const& positioner,
std::function<std::pair<int, int>(int, int)> parent_position_func)
: name{name},
expected_positon{expected_x, expected_y},
expected_size{expected_width, expected_height},
positioner{positioner},
parent_position_func{std::move(parent_position_func)}
{
}
std::string name;
std::pair<int, int> expected_positon;
std::pair<int, int> expected_size;
PositionerParams positioner;
/// parent_position_func is called with the size of the output
std::optional<std::function<std::pair<int, int>(int output_width, int output_height)>> parent_position_func;
};
std::ostream& operator<<(std::ostream& out, PositionerTestParams const& param)
{
return out << param.name;
}
class XdgPopupManagerBase
{
public:
struct State
{
int x;
int y;
int width;
int height;
};
static int const window_x, window_y;
XdgPopupManagerBase(wlcs::Server& server, std::shared_ptr<wlcs::Client> client)
: the_server{server},
client{client},
surface{*client},
parent_position_{window_x, window_y}
{
surface.add_frame_callback([this](auto) { surface_rendered = true; });
}
virtual ~XdgPopupManagerBase() = default;
void wait_for_frame_to_render()
{
surface.attach_buffer(window_width, window_height);
surface_rendered = false;
wl_surface_commit(surface);
client->dispatch_until([this]() { return surface_rendered; });
the_server.move_surface_to(surface, parent_position_.first, parent_position_.second);
}
void map_popup(PositionerParams const& params)
{
popup_surface.emplace(*client);
setup_popup(params);
wl_surface_commit(popup_surface.value());
dispatch_until_popup_configure();
popup_surface.value().attach_buffer(params.popup_size.first, params.popup_size.second);
bool surface_rendered{false};
popup_surface.value().add_frame_callback([&surface_rendered](auto) { surface_rendered = true; });
wl_surface_commit(popup_surface.value());
client->dispatch_until([&surface_rendered]() { return surface_rendered; });
}
void set_parent_position(
std::function<std::pair<int, int>(int output_width, int output_height)> const& parent_position_func)
{
auto const output_size = client->output_state(0).mode_size.value();
parent_position_ = parent_position_func(output_size.first, output_size.second);
the_server.move_surface_to(surface, parent_position_.first, parent_position_.second);
client->roundtrip();
}
auto parent_position() -> std::pair<int, int>
{
return parent_position_;
}
void unmap_popup()
{
clear_popup();
popup_surface = std::nullopt;
}
virtual auto create_child_popup() -> std::unique_ptr<XdgPopupManagerBase> = 0;
virtual void dispatch_until_popup_configure() = 0;
MOCK_METHOD0(popup_done, void());
wlcs::Server& the_server;
std::shared_ptr<wlcs::Client> const client;
wlcs::Surface surface;
std::optional<wlcs::Surface> popup_surface;
std::pair<int, int> parent_position_;
std::optional<State> state;
protected:
virtual void setup_popup(PositionerParams const& params) = 0;
virtual void clear_popup() = 0;
bool surface_rendered{true};
};
int const XdgPopupManagerBase::window_x = 500;
int const XdgPopupManagerBase::window_y = 500;
class XdgPopupStableManager : public XdgPopupManagerBase
{
public:
XdgPopupStableManager(wlcs::Server& server)
: XdgPopupManagerBase{server, std::make_shared<wlcs::Client>(server)},
xdg_shell_surface{std::in_place, *client, surface},
toplevel{std::in_place, xdg_shell_surface.value()},
parent{&*xdg_shell_surface}
{
wait_for_frame_to_render();
}
XdgPopupStableManager(wlcs::Server& server, std::shared_ptr<wlcs::Client> client, wlcs::XdgSurfaceStable* parent)
: XdgPopupManagerBase{server, client},
parent{parent}
{
}
void dispatch_until_popup_configure() override
{
client->dispatch_until(
[prev_count = popup_surface_configure_count, &current_count = popup_surface_configure_count]()
{
return current_count > prev_count;
});
}
static void setup_positioner(wlcs::XdgPositionerStable& positioner, PositionerParams const& param)
{
// size must always be set
xdg_positioner_set_size(positioner, param.popup_size.first, param.popup_size.second);
// anchor rect must always be set
xdg_positioner_set_anchor_rect(
positioner,
param.anchor_rect.first.first,
param.anchor_rect.first.second,
param.anchor_rect.second.first,
param.anchor_rect.second.second);
if (param.anchor_stable)
xdg_positioner_set_anchor(positioner, param.anchor_stable.value());
if (param.gravity_stable)
xdg_positioner_set_gravity(positioner, param.gravity_stable.value());
if (param.constraint_adjustment_stable)
xdg_positioner_set_constraint_adjustment(positioner, param.constraint_adjustment_stable.value());
if (param.offset)
xdg_positioner_set_offset(positioner, param.offset.value().first, param.offset.value().second);
if (param.reactive)
{
if (xdg_positioner_get_version(positioner) < XDG_POSITIONER_SET_REACTIVE_SINCE_VERSION)
{
BOOST_THROW_EXCEPTION(std::logic_error("XDG shell version does not support reactive popups"));
}
xdg_positioner_set_reactive(positioner);
}
}
void setup_popup(PositionerParams const& param) override
{
wlcs::XdgPositionerStable positioner{*client};
setup_positioner(positioner, param);
popup_xdg_surface.emplace(*client, popup_surface.value());
popup.emplace(popup_xdg_surface.value(), parent, positioner);
if (param.grab)
{
if (!client->latest_serial())
{
BOOST_THROW_EXCEPTION(std::runtime_error("client does not have a serial"));
}
xdg_popup_grab(popup.value().popup, client->seat(), client->latest_serial().value());
}
ON_CALL(popup_xdg_surface.value(), configure).WillByDefault([&](auto serial)
{
xdg_surface_ack_configure(popup_xdg_surface.value(), serial);
popup_surface_configure_count++;
});
ON_CALL(popup.value(), configure).WillByDefault([&](auto... args)
{
state = State{args...};
});
ON_CALL(popup.value(), done()).WillByDefault([this](){ popup_done(); });
}
void move_parent_using_pointer(std::pair<int, int> to)
{
auto pointer = the_server.create_pointer();
pointer.move_to(parent_position_.first + 1, parent_position_.second + 1);
pointer.left_button_down();
client->roundtrip();
if (client->window_under_cursor() != surface)
{
BOOST_THROW_EXCEPTION(std::runtime_error("surface not detected at expected position"));
}
xdg_toplevel_move(toplevel.value(), client->seat(), client->latest_serial().value());
client->roundtrip();
pointer.move_to(to.first + 1, to.second + 1);
parent_position_ = to;
pointer.left_button_up();
client->roundtrip();
}
void clear_popup() override
{
popup = std::nullopt;
popup_xdg_surface = std::nullopt;
}
auto create_child_popup() -> std::unique_ptr<XdgPopupManagerBase> override
{
return std::make_unique<XdgPopupStableManager>(the_server, client, &popup_xdg_surface.value());
}
std::optional<wlcs::XdgSurfaceStable> xdg_shell_surface;
std::optional<wlcs::XdgToplevelStable> toplevel;
wlcs::XdgSurfaceStable* const parent;
std::optional<wlcs::XdgSurfaceStable> popup_xdg_surface;
std::optional<wlcs::XdgPopupStable> popup;
int popup_surface_configure_count{0};
};
class XdgPopupV6Manager : public XdgPopupManagerBase
{
public:
XdgPopupV6Manager(wlcs::Server& server)
: XdgPopupManagerBase{server, std::make_shared<wlcs::Client>(server)},
xdg_shell_surface{std::in_place, *client, surface},
toplevel{std::in_place, xdg_shell_surface.value()},
parent{&*xdg_shell_surface}
{
wait_for_frame_to_render();
}
XdgPopupV6Manager(wlcs::Server& server, std::shared_ptr<wlcs::Client> client, wlcs::XdgSurfaceV6* parent)
: XdgPopupManagerBase{server, client},
parent{parent}
{
}
void dispatch_until_popup_configure() override
{
client->dispatch_until(
[prev_count = popup_surface_configure_count, &current_count = popup_surface_configure_count]()
{
return current_count > prev_count;
});
}
void setup_popup(PositionerParams const& param) override
{
wlcs::XdgPositionerV6 positioner{*client};
// size must always be set
zxdg_positioner_v6_set_size(positioner, param.popup_size.first, param.popup_size.second);
// anchor rect must always be set
zxdg_positioner_v6_set_anchor_rect(
positioner,
param.anchor_rect.first.first,
param.anchor_rect.first.second,
param.anchor_rect.second.first,
param.anchor_rect.second.second);
if (param.anchor_stable)
{
uint32_t v6_anchor = anchor_stable_to_v6(param.anchor_stable.value());
zxdg_positioner_v6_set_anchor(positioner, v6_anchor);
}
if (param.gravity_stable)
{
uint32_t v6_gravity = gravity_stable_to_v6(param.gravity_stable.value());
zxdg_positioner_v6_set_gravity(positioner, v6_gravity);
}
if (param.constraint_adjustment_stable)
{
uint32_t v6_constraint_adjustment =
constraint_adjustment_stable_to_v6(param.constraint_adjustment_stable.value());
zxdg_positioner_v6_set_constraint_adjustment(positioner, v6_constraint_adjustment);
}
if (param.offset)
{
zxdg_positioner_v6_set_offset(positioner, param.offset.value().first, param.offset.value().second);
}
popup_xdg_surface.emplace(*client, popup_surface.value());
popup.emplace(popup_xdg_surface.value(), *parent, positioner);
if (param.grab)
{
if (!client->latest_serial())
{
BOOST_THROW_EXCEPTION(std::runtime_error("client does not have a serial"));
}
zxdg_popup_v6_grab(popup.value().popup, client->seat(), client->latest_serial().value());
}
ON_CALL(popup.value(), done).WillByDefault([this](){ popup_done(); });
ON_CALL(popup_xdg_surface.value(), configure).WillByDefault([&](auto serial)
{
zxdg_surface_v6_ack_configure(popup_xdg_surface.value(), serial);
popup_surface_configure_count++;
});
ON_CALL(popup.value(), configure).WillByDefault([this](auto... args)
{
state = State{args...};
});
}
void clear_popup() override
{
popup = std::nullopt;
popup_xdg_surface = std::nullopt;
}
auto create_child_popup() -> std::unique_ptr<XdgPopupManagerBase> override
{
return std::make_unique<XdgPopupV6Manager>(the_server, client, &popup_xdg_surface.value());
}
std::optional<wlcs::XdgSurfaceV6> xdg_shell_surface;
std::optional<wlcs::XdgToplevelV6> toplevel;
wlcs::XdgSurfaceV6* const parent;
std::optional<wlcs::XdgSurfaceV6> popup_xdg_surface;
std::optional<wlcs::XdgPopupV6> popup;
int popup_surface_configure_count{0};
};
class LayerV1PopupManager : public XdgPopupManagerBase
{
public:
LayerV1PopupManager(wlcs::Server& server)
: XdgPopupManagerBase{server, std::make_shared<wlcs::Client>(server)},
layer_surface{*client, surface}
{
{
wlcs::Client client{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_size(layer_surface, window_width, window_height);
zwlr_layer_surface_v1_set_keyboard_interactivity(
layer_surface,
ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_ON_DEMAND);
wait_for_frame_to_render();
}
void dispatch_until_popup_configure() override
{
client->dispatch_until(
[prev_count = popup_surface_configure_count, &current_count = popup_surface_configure_count]()
{
return current_count > prev_count;
});
}
void setup_popup(PositionerParams const& param) override
{
wlcs::XdgPositionerStable positioner{*client};
// size must always be set
xdg_positioner_set_size(positioner, param.popup_size.first, param.popup_size.second);
// anchor rect must always be set
xdg_positioner_set_anchor_rect(
positioner,
param.anchor_rect.first.first,
param.anchor_rect.first.second,
param.anchor_rect.second.first,
param.anchor_rect.second.second);
if (param.anchor_stable)
xdg_positioner_set_anchor(positioner, param.anchor_stable.value());
if (param.gravity_stable)
xdg_positioner_set_gravity(positioner, param.gravity_stable.value());
if (param.constraint_adjustment_stable)
xdg_positioner_set_constraint_adjustment(positioner, param.constraint_adjustment_stable.value());
if (param.offset)
xdg_positioner_set_offset(positioner, param.offset.value().first, param.offset.value().second);
popup_xdg_surface.emplace(*client, popup_surface.value());
popup.emplace(popup_xdg_surface.value(), std::nullopt, positioner);
zwlr_layer_surface_v1_get_popup(layer_surface, popup.value());
if (param.grab)
{
if (!client->latest_serial())
{
BOOST_THROW_EXCEPTION(std::runtime_error("client does not have a serial"));
}
xdg_popup_grab(popup.value().popup, client->seat(), client->latest_serial().value());
}
ON_CALL(popup_xdg_surface.value(), configure).WillByDefault([&](uint32_t serial)
{
xdg_surface_ack_configure(popup_xdg_surface.value(), serial);
popup_surface_configure_count++;
});
ON_CALL(popup.value(), configure).WillByDefault([this](auto... args)
{
state = State{args...};
});
ON_CALL(popup.value(), done()).WillByDefault([this](){ popup_done(); });
}
void clear_popup() override
{
popup = std::nullopt;
popup_xdg_surface = std::nullopt;
}
auto create_child_popup() -> std::unique_ptr<XdgPopupManagerBase> override
{
return std::make_unique<XdgPopupStableManager>(the_server, client, &popup_xdg_surface.value());
}
wlcs::LayerSurfaceV1 layer_surface;
std::optional<wlcs::XdgSurfaceStable> popup_xdg_surface;
std::optional<wlcs::XdgPopupStable> popup;
int popup_surface_configure_count{0};
};
auto surface_actually_at_location(
wlcs::Server& server,
wlcs::Client& client,
wl_surface* surface,
std::pair<int, int> location) -> bool
{
auto pointer = server.create_pointer();
int offset_x = 1, offset_y = 1;
if (location.first < 0)
{
offset_x -= location.first;
}
if (location.second < 0)
{
offset_y -= location.second;
}
pointer.move_to(location.first + offset_x, location.second + offset_y);
client.roundtrip();
return (
client.window_under_cursor() == surface &&
client.pointer_position() == std::make_pair(wl_fixed_from_int(offset_x), wl_fixed_from_int(offset_y)));
}
}
class XdgPopupPositionerTest:
public wlcs::StartedInProcessServer,
public testing::WithParamInterface<PositionerTestParams>
{
};
TEST_P(XdgPopupPositionerTest, xdg_shell_stable_popup_placed_correctly)
{
auto manager = std::make_unique<XdgPopupStableManager>(the_server());
auto const& param = GetParam();
if (param.parent_position_func)
{
manager->set_parent_position(param.parent_position_func.value());
}
manager->map_popup(param.positioner);
ASSERT_THAT(
manager->state,
Ne(std::nullopt)) << "popup configure event not sent";
EXPECT_THAT(
std::make_pair(manager->state.value().x, manager->state.value().y),
Eq(param.expected_positon)) << "popup placed in incorrect position";
EXPECT_THAT(
std::make_pair(manager->state.value().width, manager->state.value().height),
Eq(param.expected_size)) << "popup has incorrect size";
EXPECT_THAT(
surface_actually_at_location(
the_server(),
*manager->client,
manager->popup_surface.value(),
std::make_pair(
manager->parent_position().first + param.expected_positon.first,
manager->parent_position().second + param.expected_positon.second)),
IsTrue());
}
TEST_P(XdgPopupPositionerTest, xdg_shell_unstable_v6_popup_placed_correctly)
{
auto manager = std::make_unique<XdgPopupV6Manager>(the_server());
auto const& param = GetParam();
if (param.parent_position_func)
{
manager->set_parent_position(param.parent_position_func.value());
}
manager->map_popup(param.positioner);
ASSERT_THAT(
manager->state,
Ne(std::nullopt)) << "popup configure event not sent";
EXPECT_THAT(
std::make_pair(manager->state.value().x, manager->state.value().y),
Eq(param.expected_positon)) << "popup placed in incorrect position";
EXPECT_THAT(
std::make_pair(manager->state.value().width, manager->state.value().height),
Eq(param.expected_size)) << "popup has incorrect size";
EXPECT_THAT(
surface_actually_at_location(
the_server(),
*manager->client,
manager->popup_surface.value(),
std::make_pair(
manager->parent_position().first + param.expected_positon.first,
manager->parent_position().second + param.expected_positon.second)),
IsTrue());
}
TEST_P(XdgPopupPositionerTest, layer_shell_popup_placed_correctly)
{
auto manager = std::make_unique<LayerV1PopupManager>(the_server());
auto const& param = GetParam();
if (param.parent_position_func)
{
manager->set_parent_position(param.parent_position_func.value());
}
manager->map_popup(param.positioner);
ASSERT_THAT(
manager->state,
Ne(std::nullopt)) << "popup configure event not sent";
EXPECT_THAT(
std::make_pair(manager->state.value().x, manager->state.value().y),
Eq(param.expected_positon)) << "popup placed in incorrect position";
EXPECT_THAT(
std::make_pair(manager->state.value().width, manager->state.value().height),
Eq(param.expected_size)) << "popup has incorrect size";
EXPECT_THAT(
surface_actually_at_location(
the_server(),
*manager->client,
manager->popup_surface.value(),
std::make_pair(
manager->parent_position().first + param.expected_positon.first,
manager->parent_position().second + param.expected_positon.second)),
IsTrue());
}
INSTANTIATE_TEST_SUITE_P(
Default,
XdgPopupPositionerTest,
testing::Values(
PositionerTestParams{"default values", (window_width - popup_width) / 2, (window_height - popup_height) / 2, PositionerParams()}
));
INSTANTIATE_TEST_SUITE_P(
Anchor,
XdgPopupPositionerTest,
testing::Values(
PositionerTestParams{"anchor left", -popup_width / 2, (window_height - popup_height) / 2,
PositionerParams().with_anchor(XDG_POSITIONER_ANCHOR_LEFT)},
PositionerTestParams{"anchor right", window_width - popup_width / 2, (window_height - popup_height) / 2,
PositionerParams().with_anchor(XDG_POSITIONER_ANCHOR_RIGHT)},
PositionerTestParams{"anchor top", (window_width - popup_width) / 2, -popup_height / 2,
PositionerParams().with_anchor(XDG_POSITIONER_ANCHOR_TOP)},
PositionerTestParams{"anchor bottom", (window_width - popup_width) / 2, window_height - popup_height / 2,
PositionerParams().with_anchor(XDG_POSITIONER_ANCHOR_BOTTOM)},
PositionerTestParams{"anchor top left", -popup_width / 2, -popup_height / 2,
PositionerParams().with_anchor(XDG_POSITIONER_ANCHOR_TOP_LEFT)},
PositionerTestParams{"anchor top right", window_width - popup_width / 2, -popup_height / 2,
PositionerParams().with_anchor(XDG_POSITIONER_ANCHOR_TOP_RIGHT)},
PositionerTestParams{"anchor bottom left", -popup_width / 2, window_height - popup_height / 2,
PositionerParams().with_anchor(XDG_POSITIONER_ANCHOR_BOTTOM_LEFT)},
PositionerTestParams{"anchor bottom right", window_width - popup_width / 2, window_height - popup_height / 2,
PositionerParams().with_anchor(XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT)}
));
INSTANTIATE_TEST_SUITE_P(
Gravity,
XdgPopupPositionerTest,
testing::Values(
PositionerTestParams{"gravity none", (window_width - popup_width) / 2, (window_height - popup_height) / 2,
PositionerParams().with_gravity(XDG_POSITIONER_GRAVITY_NONE)},
PositionerTestParams{"gravity left", window_width / 2 - popup_width, (window_height - popup_height) / 2,
PositionerParams().with_gravity(XDG_POSITIONER_GRAVITY_LEFT)},
PositionerTestParams{"gravity right", window_width / 2, (window_height - popup_height) / 2,
PositionerParams().with_gravity(XDG_POSITIONER_GRAVITY_RIGHT)},
PositionerTestParams{"gravity top", (window_width - popup_width) / 2, window_height / 2 - popup_height,
PositionerParams().with_gravity(XDG_POSITIONER_GRAVITY_TOP)},
PositionerTestParams{"gravity bottom", (window_width - popup_width) / 2, window_height / 2,
PositionerParams().with_gravity(XDG_POSITIONER_GRAVITY_BOTTOM)},
PositionerTestParams{"gravity top left", window_width / 2 - popup_width, window_height / 2 - popup_height,
PositionerParams().with_gravity(XDG_POSITIONER_GRAVITY_TOP_LEFT)},
PositionerTestParams{"gravity top right", window_width / 2, window_height / 2 - popup_height,
PositionerParams().with_gravity(XDG_POSITIONER_GRAVITY_TOP_RIGHT)},
PositionerTestParams{"gravity bottom left", window_width / 2 - popup_width, window_height / 2,
PositionerParams().with_gravity(XDG_POSITIONER_GRAVITY_BOTTOM_LEFT)},
PositionerTestParams{"gravity bottom right", window_width / 2, window_height / 2,
PositionerParams().with_gravity(XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT)}
));
INSTANTIATE_TEST_SUITE_P(
AnchorRect,
XdgPopupPositionerTest,
testing::Values(
PositionerTestParams{"explicit defaultPositionerParams anchor rect", (window_width - popup_width) / 2, (window_height - popup_height) / 2,
PositionerParams().with_anchor_rect(0, 0, window_width, window_height)},
PositionerTestParams{"upper left anchor rect", (window_width - 40 - popup_width) / 2, (window_height - 30 - popup_height) / 2,
PositionerParams().with_anchor_rect(0, 0, window_width - 40, window_height - 30)},
PositionerTestParams{"upper right anchor rect", (window_width + 40 - popup_width) / 2, (window_height - 30 - popup_height) / 2,
PositionerParams().with_anchor_rect(40, 0, window_width - 40, window_height - 30)},
PositionerTestParams{"lower left anchor rect", (window_width - 40 - popup_width) / 2, (window_height + 30 - popup_height) / 2,
PositionerParams().with_anchor_rect(0, 30, window_width - 40, window_height - 30)},
PositionerTestParams{"lower right anchor rect", (window_width + 40 - popup_width) / 2, (window_height + 30 - popup_height) / 2,
PositionerParams().with_anchor_rect(40, 30, window_width - 40, window_height - 30)},
PositionerTestParams{"offset anchor rect", (window_width - 40 - popup_width) / 2, (window_height - 80 - popup_height) / 2,
PositionerParams().with_anchor_rect(20, 20, window_width - 80, window_height - 120)}
));
INSTANTIATE_TEST_SUITE_P(
ConstraintAdjustmentNone,
XdgPopupPositionerTest,
testing::Values(
PositionerTestParams{"middle of screen",
-popup_width, -popup_height,
popup_width, popup_height,
PositionerParams()
.with_anchor(XDG_POSITIONER_ANCHOR_TOP_LEFT)
.with_gravity(XDG_POSITIONER_GRAVITY_TOP_LEFT)
.with_constraint_adjustment(XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_NONE),
[](int width, int height){ return std::make_pair((width - window_width) / 2, (height - window_height) / 2); }},
PositionerTestParams{"off top left edge",
-popup_width, -popup_height,
popup_width, popup_height,
PositionerParams()
.with_anchor(XDG_POSITIONER_ANCHOR_TOP_LEFT)
.with_gravity(XDG_POSITIONER_GRAVITY_TOP_LEFT)
.with_constraint_adjustment(XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_NONE),
[](int /*width*/, int /*height*/){ return std::make_pair(5, 5); }},
PositionerTestParams{"off top right edge",
window_width, -popup_height,
popup_width, popup_height,
PositionerParams()
.with_anchor(XDG_POSITIONER_ANCHOR_TOP_RIGHT)
.with_gravity(XDG_POSITIONER_GRAVITY_TOP_RIGHT)
.with_constraint_adjustment(XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_NONE),
[](int width, int /*height*/){ return std::make_pair(width - window_width - 5, 5); }},
PositionerTestParams{"off bottom left edge",
-popup_width, window_height,
popup_width, popup_height,
PositionerParams()
.with_anchor(XDG_POSITIONER_ANCHOR_BOTTOM_LEFT)
.with_gravity(XDG_POSITIONER_GRAVITY_BOTTOM_LEFT)
.with_constraint_adjustment(XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_NONE),
[](int /*width*/, int height){ return std::make_pair(5, height - window_height - 5); }},
PositionerTestParams{"off bottom right edge",
window_width, window_height,
popup_width, popup_height,
PositionerParams()
.with_anchor(XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT)
.with_gravity(XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT)
.with_constraint_adjustment(XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_NONE),
[](int width, int height){ return std::make_pair(width - window_width - 5, height - window_height - 5); }}
));
INSTANTIATE_TEST_SUITE_P(
ConstraintAdjustmentSlide,
XdgPopupPositionerTest,
testing::Values(
PositionerTestParams{"middle of screen",
-popup_width, -popup_height,
popup_width, popup_height,
PositionerParams()
.with_anchor(XDG_POSITIONER_ANCHOR_TOP_LEFT)
.with_gravity(XDG_POSITIONER_GRAVITY_TOP_LEFT)
.with_constraint_adjustment(
XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y),
[](int width, int height){ return std::make_pair((width - window_width) / 2, (height - window_height) / 2); }},
PositionerTestParams{"off top left edge",
-5, -5,
popup_width, popup_height,
PositionerParams()
.with_anchor(XDG_POSITIONER_ANCHOR_TOP_LEFT)
.with_gravity(XDG_POSITIONER_GRAVITY_TOP_LEFT)
.with_constraint_adjustment(
XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y),
[](int /*width*/, int /*height*/){ return std::make_pair(5, 5); }},
PositionerTestParams{"off top right edge",
window_width - popup_width + 5, -5,
popup_width, popup_height,
PositionerParams()
.with_anchor(XDG_POSITIONER_ANCHOR_TOP_RIGHT)
.with_gravity(XDG_POSITIONER_GRAVITY_TOP_RIGHT)
.with_constraint_adjustment(
XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y),
[](int width, int /*height*/){ return std::make_pair(width - window_width - 5, 5); }},
PositionerTestParams{"off bottom left edge", -5,
window_height - popup_height + 5,
popup_width, popup_height,
PositionerParams()
.with_anchor(XDG_POSITIONER_ANCHOR_BOTTOM_LEFT)
.with_gravity(XDG_POSITIONER_GRAVITY_BOTTOM_LEFT)
.with_constraint_adjustment(
XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y),
[](int /*width*/, int height){ return std::make_pair(5, height - window_height - 5); }},
PositionerTestParams{"off bottom right edge",
window_width - popup_width + 5, window_height - popup_height + 5,
popup_width, popup_height,
PositionerParams()
.with_anchor(XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT)
.with_gravity(XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT)
.with_constraint_adjustment(
XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y),
[](int width, int height){ return std::make_pair(width - window_width - 5, height - window_height - 5); }}
));
INSTANTIATE_TEST_SUITE_P(
ConstraintAdjustmentFlip,
XdgPopupPositionerTest,
testing::Values(
PositionerTestParams{"middle of screen",
-popup_width, -popup_height,
popup_width, popup_height,
PositionerParams()
.with_anchor(XDG_POSITIONER_ANCHOR_TOP_LEFT)
.with_gravity(XDG_POSITIONER_GRAVITY_TOP_LEFT)
.with_constraint_adjustment(
XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_X | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y),
[](int width, int height){ return std::make_pair((width - window_width) / 2, (height - window_height) / 2); }},
PositionerTestParams{"off top left edge",
window_width, window_height,
popup_width, popup_height,
PositionerParams()
.with_anchor(XDG_POSITIONER_ANCHOR_TOP_LEFT)
.with_gravity(XDG_POSITIONER_GRAVITY_TOP_LEFT)
.with_constraint_adjustment(
XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_X | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y),
[](int /*width*/, int /*height*/){ return std::make_pair(5, 5); }},
PositionerTestParams{"off top right edge",
-popup_width, window_height,
popup_width, popup_height,
PositionerParams()
.with_anchor(XDG_POSITIONER_ANCHOR_TOP_RIGHT)
.with_gravity(XDG_POSITIONER_GRAVITY_TOP_RIGHT)
.with_constraint_adjustment(
XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_X | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y),
[](int width, int /*height*/){ return std::make_pair(width - window_width - 5, 5); }},
PositionerTestParams{"off bottom left edge",
window_width, -popup_height,
popup_width, popup_height,
PositionerParams()
.with_anchor(XDG_POSITIONER_ANCHOR_BOTTOM_LEFT)
.with_gravity(XDG_POSITIONER_GRAVITY_BOTTOM_LEFT)
.with_constraint_adjustment(
XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_X | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y),
[](int /*width*/, int height){ return std::make_pair(5, height - window_height - 5); }},
PositionerTestParams{"off bottom right edge",
-popup_width, -popup_height,
popup_width, popup_height,
PositionerParams()
.with_anchor(XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT)
.with_gravity(XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT)
.with_constraint_adjustment(
XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_X | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y),
[](int width, int height){ return std::make_pair(width - window_width - 5, height - window_height - 5); }}
));
INSTANTIATE_TEST_SUITE_P(
ConstraintAdjustmentResize,
XdgPopupPositionerTest,
testing::Values(
PositionerTestParams{"middle of screen",
-popup_width, -popup_height,
popup_width, popup_height,
PositionerParams()
.with_anchor(XDG_POSITIONER_ANCHOR_TOP_LEFT)
.with_gravity(XDG_POSITIONER_GRAVITY_TOP_LEFT)
.with_constraint_adjustment(
XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_X | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_Y),
[](int width, int height){ return std::make_pair((width - window_width) / 2, (height - window_height) / 2); }},
PositionerTestParams{"off top left edge",
-5, -5,
5, 5,
PositionerParams()
.with_anchor(XDG_POSITIONER_ANCHOR_TOP_LEFT)
.with_gravity(XDG_POSITIONER_GRAVITY_TOP_LEFT)
.with_constraint_adjustment(
XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_X | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_Y),
[](int /*width*/, int /*height*/){ return std::make_pair(5, 5); }},
PositionerTestParams{"off top right edge",
window_width, -5,
5, 5,
PositionerParams()
.with_anchor(XDG_POSITIONER_ANCHOR_TOP_RIGHT)
.with_gravity(XDG_POSITIONER_GRAVITY_TOP_RIGHT)
.with_constraint_adjustment(
XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_X | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_Y),
[](int width, int /*height*/){ return std::make_pair(width - window_width - 5, 5); }},
PositionerTestParams{"off bottom left edge",
-5, window_height,
5, 5,
PositionerParams()
.with_anchor(XDG_POSITIONER_ANCHOR_BOTTOM_LEFT)
.with_gravity(XDG_POSITIONER_GRAVITY_BOTTOM_LEFT)
.with_constraint_adjustment(
XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_X | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_Y),
[](int /*width*/, int height){ return std::make_pair(5, height - window_height - 5); }},
PositionerTestParams{"off bottom right edge",
window_width, window_height,
5, 5,
PositionerParams()
.with_anchor(XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT)
.with_gravity(XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT)
.with_constraint_adjustment(
XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_X | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_Y),
[](int width, int height){ return std::make_pair(width - window_width - 5, height - window_height - 5); }}
));
struct XdgPopupTestParam
{
std::function<std::unique_ptr<XdgPopupManagerBase>(wlcs::InProcessServer* const)> build;
};
std::ostream& operator<<(std::ostream& out, XdgPopupTestParam const&) { return out; }
class XdgPopupTest:
public wlcs::StartedInProcessServer,
public testing::WithParamInterface<XdgPopupTestParam>
{
};
TEST_P(XdgPopupTest, pointer_focus_goes_to_popup)
{
auto const& param = GetParam();
auto manager = param.build(this);
auto pointer = the_server().create_pointer();
pointer.move_to(manager->window_x + 1, manager->window_y + 1);
manager->client->roundtrip();
EXPECT_THAT(manager->client->window_under_cursor(), Eq((wl_surface*)manager->surface));
auto positioner = PositionerParams{}
.with_size(30, 30)
.with_anchor(XDG_POSITIONER_ANCHOR_TOP_LEFT)
.with_gravity(XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT);
manager->map_popup(positioner);
manager->client->roundtrip();
pointer.move_to(manager->window_x + 2, manager->window_y + 1);
manager->client->roundtrip();
EXPECT_THAT(manager->client->window_under_cursor(), Eq((wl_surface*)manager->popup_surface.value()));
}
TEST_P(XdgPopupTest, popup_gives_up_pointer_focus_when_gone)
{
auto const& param = GetParam();
auto manager = param.build(this);
auto pointer = the_server().create_pointer();
auto positioner = PositionerParams{}
.with_size(30, 30)
.with_anchor(XDG_POSITIONER_ANCHOR_TOP_LEFT)
.with_gravity(XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT);
manager->map_popup(positioner);
manager->client->roundtrip();
pointer.move_to(manager->window_x + 2, manager->window_y + 1);
manager->client->roundtrip();
EXPECT_THAT(manager->client->window_under_cursor(), Eq((wl_surface*)manager->popup_surface.value()));
manager->unmap_popup();
manager->client->roundtrip();
pointer.move_to(manager->window_x + 3, manager->window_y + 1);
manager->client->roundtrip();
EXPECT_THAT(manager->client->window_under_cursor(), Eq((wl_surface*)manager->surface));
}
TEST_P(XdgPopupTest, grabbed_popup_gets_done_event_when_new_toplevel_created)
{
auto const& param = GetParam();
auto manager = param.build(this);
auto pointer = the_server().create_pointer();
// This is needed to get a serial, which will be used later on
pointer.move_to(manager->window_x + 2, manager->window_y + 2);
pointer.left_click();
manager->client->roundtrip();
auto positioner = PositionerParams{}
.with_size(30, 30)
.with_anchor(XDG_POSITIONER_ANCHOR_TOP_LEFT)
.with_gravity(XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT)
.with_grab();
manager->map_popup(positioner);
manager->client->roundtrip();
EXPECT_CALL(*manager, popup_done());
manager->client->create_visible_surface(window_width, window_height);
}
TEST_P(XdgPopupTest, grabbed_popups_get_done_events_in_correct_order)
{
auto const& param = GetParam();
wlcs::Client background_client{the_server()};
wlcs::Surface background_surface{background_client.create_visible_surface(10, 10)};
the_server().move_surface_to(
background_surface,
XdgPopupManagerBase::window_x - 5,
XdgPopupManagerBase::window_y - 5);
auto top_popup_manager = param.build(this);
auto pointer = the_server().create_pointer();
// This is needed to get a serial, which will be used later on
pointer.move_to(top_popup_manager->window_x + 2, top_popup_manager->window_y + 2);
pointer.left_click();
top_popup_manager->client->roundtrip();
auto positioner = PositionerParams{}
.with_anchor_rect(20, 20, 1, 1)
.with_size(30, 30)
.with_anchor(XDG_POSITIONER_ANCHOR_TOP_LEFT)
.with_gravity(XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT)
.with_grab();
top_popup_manager->map_popup(positioner);
top_popup_manager->client->roundtrip();
auto sub_popup_manager = top_popup_manager->create_child_popup();
sub_popup_manager->map_popup(positioner);
top_popup_manager->client->roundtrip();
InSequence seq;
EXPECT_CALL(*sub_popup_manager, popup_done());
EXPECT_CALL(*top_popup_manager, popup_done());
// Click on background surface so it is focused and grabbed popups are dismissed
pointer.move_to(top_popup_manager->window_x - 2, top_popup_manager->window_y - 2);
pointer.left_click();
top_popup_manager->client->roundtrip();
}
TEST_P(XdgPopupTest, grabbed_popup_gets_keyboard_focus)
{
auto const& param = GetParam();
auto manager = param.build(this);
auto pointer = the_server().create_pointer();
// This is needed to get a serial, which will be used later on
pointer.move_to(manager->window_x + 2, manager->window_y + 2);
pointer.left_click();
manager->client->roundtrip();
auto positioner = PositionerParams{}
.with_size(30, 30)
.with_anchor(XDG_POSITIONER_ANCHOR_TOP_LEFT)
.with_gravity(XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT)
.with_grab();
manager->map_popup(positioner);
manager->client->roundtrip();
EXPECT_THAT(
manager->client->keyboard_focused_window(),
Eq(static_cast<wl_surface*>(manager->popup_surface.value())))
<< "grabbed popup not given keyboard focus";
}
TEST_P(XdgPopupTest, non_grabbed_popup_does_not_get_keyboard_focus)
{
auto const& param = GetParam();
auto manager = param.build(this);
auto positioner = PositionerParams{}
.with_size(30, 30)
.with_anchor(XDG_POSITIONER_ANCHOR_TOP_LEFT)
.with_gravity(XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT);
manager->map_popup(positioner);
manager->client->roundtrip();
EXPECT_THAT(
manager->client->keyboard_focused_window(),
Ne(static_cast<wl_surface*>(manager->popup_surface.value())))
<< "popup given keyboard focus";
EXPECT_THAT(
manager->client->keyboard_focused_window(),
Eq(static_cast<wl_surface*>(manager->surface)));
}
TEST_P(XdgPopupTest, does_not_get_popup_done_event_before_button_press)
{
auto const& param = GetParam();
auto manager = param.build(this);
auto pointer = the_server().create_pointer();
// This is needed to get a serial, which will be used later on
pointer.move_to(manager->window_x + 2, manager->window_y + 2);
pointer.left_click();
manager->client->roundtrip();
auto positioner = PositionerParams{}
.with_size(30, 30)
.with_anchor(XDG_POSITIONER_ANCHOR_TOP_LEFT)
.with_gravity(XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT)
.with_grab();
manager->map_popup(positioner);
manager->client->roundtrip();
// This may or may not be sent, but a button press should not come in after it if it is sent
bool got_popup_done = false;
EXPECT_CALL(*manager, popup_done()).Times(AnyNumber()).WillRepeatedly([&]()
{
got_popup_done = true;
});
manager->client->add_pointer_button_notification([&](auto, auto, auto)
{
EXPECT_THAT(got_popup_done, IsFalse()) << "pointer button sent after popup done";
return true;
});
pointer.move_to(manager->window_x + 32, manager->window_y + 32);
pointer.left_click();
manager->client->roundtrip();
}
TEST_F(XdgPopupTest, zero_size_anchor_rect_stable)
{
auto manager = std::make_unique<XdgPopupStableManager>(the_server());
auto positioner = PositionerParams{}
.with_anchor_rect(window_width / 2, window_height / 2, 0, 0);
manager->map_popup(positioner);
manager->client->roundtrip();
ASSERT_THAT(
std::make_pair(manager->state.value().x, manager->state.value().y),
Eq(std::make_pair(
(window_width - popup_width) / 2,
(window_height - popup_height) / 2))) << "popup placed in incorrect position";
}
// regression test for https://github.com/MirServer/mir/issues/836
TEST_P(XdgPopupTest, popup_configure_is_valid)
{
auto const& param = GetParam();
auto manager = param.build(this);
auto positioner = PositionerParams{}
.with_size(30, 30)
.with_anchor(XDG_POSITIONER_ANCHOR_TOP_LEFT)
.with_gravity(XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT);
manager->map_popup(positioner);
manager->client->roundtrip();
ASSERT_THAT(manager->state, Ne(std::nullopt));
EXPECT_THAT(manager->state.value().width, Gt(0));
EXPECT_THAT(manager->state.value().height, Gt(0));
}
TEST_F(XdgPopupTest, when_parent_surface_is_moved_a_reactive_popup_is_moved)
{
XdgPopupStableManager manager{the_server()};
manager.client->bind_if_supported<xdg_wm_base>(wlcs::AtLeastVersion{XDG_POSITIONER_SET_REACTIVE_SINCE_VERSION});
auto positioner = PositionerParams{}
.with_anchor(XDG_POSITIONER_ANCHOR_TOP_LEFT)
.with_gravity(XDG_POSITIONER_GRAVITY_TOP_LEFT)
.with_constraint_adjustment(
XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y)
.with_reactive();
manager.map_popup(positioner);
// repositioned should not be called unless reposition is called
EXPECT_CALL(manager.popup.value(), repositioned).Times(0);
manager.client->roundtrip();
ASSERT_THAT(
manager.state,
Ne(std::nullopt)) << "popup configure event not sent";
ASSERT_THAT(
std::make_pair(manager.state.value().x, manager.state.value().y),
Eq(std::make_pair(-popup_width, -popup_height))) << "popup initially placed in incorrect position";
manager.move_parent_using_pointer({5, 5});
ASSERT_THAT(
std::make_pair(manager.state.value().x, manager.state.value().y),
Ne(std::make_pair(-popup_width, -popup_height))) << "reactive popup was not moved";
EXPECT_THAT(
std::make_pair(manager.state.value().x, manager.state.value().y),
Eq(std::make_pair(-5, -5))) << "reactive popup placed in incorrect position";
EXPECT_THAT(
surface_actually_at_location(
the_server(),
*manager.client,
manager.popup_surface.value(),
std::make_pair(0, 0)),
IsTrue());
}
TEST_F(XdgPopupTest, when_parent_surface_is_moved_a_nonreactive_popup_is_not_moved)
{
XdgPopupStableManager manager{the_server()};
manager.client->bind_if_supported<xdg_wm_base>(wlcs::AtLeastVersion{XDG_POSITIONER_SET_REACTIVE_SINCE_VERSION});
auto positioner = PositionerParams{}
.with_anchor(XDG_POSITIONER_ANCHOR_TOP_LEFT)
.with_gravity(XDG_POSITIONER_GRAVITY_TOP_LEFT)
.with_constraint_adjustment(
XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y);
manager.map_popup(positioner);
// repositioned should not be called unless reposition is called
EXPECT_CALL(manager.popup.value(), repositioned).Times(0);
manager.client->roundtrip();
ASSERT_THAT(
manager.state,
Ne(std::nullopt)) << "popup configure event not sent";
ASSERT_THAT(
std::make_pair(manager.state.value().x, manager.state.value().y),
Eq(std::make_pair(-popup_width, -popup_height))) << "popup initially placed in incorrect position";
manager.move_parent_using_pointer({5, 5});
EXPECT_THAT(
std::make_pair(manager.state.value().x, manager.state.value().y),
Eq(std::make_pair(-popup_width, -popup_height))) << "nonreactive popup was moved";
EXPECT_THAT(
surface_actually_at_location(
the_server(),
*manager.client,
manager.popup_surface.value(),
std::make_pair(5 - popup_width, 5 - popup_height)),
IsTrue());
}
TEST_F(XdgPopupTest, popup_can_be_repositioned)
{
XdgPopupStableManager manager{the_server()};
manager.client->bind_if_supported<xdg_wm_base>(wlcs::AtLeastVersion{XDG_POPUP_REPOSITION_SINCE_VERSION});
auto positioner_a = PositionerParams{}
.with_anchor(XDG_POSITIONER_ANCHOR_TOP_LEFT)
.with_gravity(XDG_POSITIONER_GRAVITY_TOP_LEFT);
manager.map_popup(positioner_a);
EXPECT_CALL(manager.popup.value(), repositioned).Times(0);
manager.client->roundtrip();
auto positioner_b = PositionerParams{}
.with_anchor(XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT)
.with_gravity(XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT);
wlcs::XdgPositionerStable xdg_positioner{*manager.client};
XdgPopupStableManager::setup_positioner(xdg_positioner, positioner_b);
xdg_popup_reposition(manager.popup.value(), xdg_positioner, 1);
EXPECT_CALL(manager.popup.value(), repositioned(1));
manager.client->roundtrip();
ASSERT_THAT(
manager.state,
Ne(std::nullopt)) << "popup configure event not sent";
EXPECT_THAT(
std::make_pair(manager.state.value().x, manager.state.value().y),
Eq(std::make_pair(window_width, window_height)));
EXPECT_THAT(
surface_actually_at_location(
the_server(),
*manager.client,
manager.popup_surface.value(),
std::make_pair(
manager.parent_position().first + window_width,
manager.parent_position().second + window_height)),
IsTrue());
}
INSTANTIATE_TEST_SUITE_P(
XdgPopupStable,
XdgPopupTest,
testing::Values(XdgPopupTestParam{
[](wlcs::InProcessServer* const server)
{
return std::make_unique<XdgPopupStableManager>(server->the_server());
}}));
INSTANTIATE_TEST_SUITE_P(
XdgPopupUnstableV6,
XdgPopupTest,
testing::Values(XdgPopupTestParam{
[](wlcs::InProcessServer* const server)
{
return std::make_unique<XdgPopupV6Manager>(server->the_server());
}}));
INSTANTIATE_TEST_SUITE_P(
LayerShellPopup,
XdgPopupTest,
testing::Values(XdgPopupTestParam{
[](wlcs::InProcessServer* const server)
{
return std::make_unique<LayerV1PopupManager>(server->the_server());
}}));
// TODO: test that positioner is always overlapping or adjacent to parent
// TODO: test that positioner is copied immediately after use
// TODO: test that error is raised when incomplete positioner is used (positioner without size and anchor rect set)
// TODO: test set_size
// TODO: test that set_window_geometry affects anchor rect
// TODO: test set_offset
// TODO: test that a zero size anchor rect fails on v6