| /* |
| * 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 <gmock/gmock.h> |
| |
| #include <experimental/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(xdg_positioner_constraint_adjustment value) -> PositionerParams& { constraint_adjustment_stable = {value}; return *this; } |
| auto with_offset(int x, int y) -> PositionerParams& { offset = {{x, y}}; 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::experimental::optional<xdg_positioner_anchor> anchor_stable; |
| std::experimental::optional<xdg_positioner_gravity> gravity_stable; |
| std::experimental::optional<xdg_positioner_constraint_adjustment> constraint_adjustment_stable; |
| std::experimental::optional<std::pair<int, int>> offset; |
| 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}, |
| positioner{positioner} |
| { |
| } |
| |
| std::string name; |
| std::pair<int, int> expected_positon; |
| PositionerParams positioner; |
| }; |
| |
| 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 = 500, window_y = 500; |
| |
| XdgPopupManagerBase(wlcs::InProcessServer* const in_process_server) |
| : the_server{in_process_server->the_server()}, |
| client{the_server}, |
| surface{client} |
| { |
| 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, window_x, window_y); |
| } |
| |
| 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 unmap_popup() |
| { |
| clear_popup(); |
| popup_surface = std::experimental::nullopt; |
| } |
| |
| virtual auto popup_position() const -> std::experimental::optional<std::pair<int, int>> = 0; |
| virtual void dispatch_until_popup_configure() = 0; |
| |
| MOCK_METHOD0(popup_done, void()); |
| |
| wlcs::Server& the_server; |
| |
| wlcs::Client client; |
| wlcs::Surface surface; |
| std::experimental::optional<wlcs::Surface> popup_surface; |
| |
| std::experimental::optional<State> state; |
| |
| protected: |
| virtual void setup_popup(PositionerParams const& params) = 0; |
| virtual void clear_popup() = 0; |
| |
| bool surface_rendered{true}; |
| }; |
| |
| class XdgPopupStableManager : public XdgPopupManagerBase |
| { |
| public: |
| XdgPopupStableManager(wlcs::InProcessServer* const in_process_server) |
| : XdgPopupManagerBase{in_process_server}, |
| xdg_shell_surface{client, surface}, |
| toplevel{xdg_shell_surface} |
| { |
| wait_for_frame_to_render(); |
| } |
| |
| void dispatch_until_popup_configure() override |
| { |
| client.dispatch_until( |
| [prev_count = popup_surface_configure_count, ¤t_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(), &xdg_shell_surface, 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()); |
| } |
| |
| popup.value().add_close_notification([this](){ popup_done(); }); |
| popup_xdg_surface.value().add_configure_notification([&](uint32_t serial) |
| { |
| xdg_surface_ack_configure(popup_xdg_surface.value(), serial); |
| popup_surface_configure_count++; |
| }); |
| |
| popup.value().add_configure_notification([this](int32_t x, int32_t y, int32_t width, int32_t height) |
| { |
| state = State{x, y, width, height}; |
| }); |
| } |
| |
| void clear_popup() override |
| { |
| popup = std::experimental::nullopt; |
| popup_xdg_surface = std::experimental::nullopt; |
| } |
| |
| auto popup_position() const -> std::experimental::optional<std::pair<int, int>> override |
| { |
| return std::make_pair(state.value().x, state.value().y); |
| } |
| |
| wlcs::XdgSurfaceStable xdg_shell_surface; |
| wlcs::XdgToplevelStable toplevel; |
| |
| std::experimental::optional<wlcs::XdgSurfaceStable> popup_xdg_surface; |
| std::experimental::optional<wlcs::XdgPopupStable> popup; |
| |
| int popup_surface_configure_count{0}; |
| }; |
| |
| class XdgPopupV6Manager : public XdgPopupManagerBase |
| { |
| public: |
| XdgPopupV6Manager(wlcs::InProcessServer* const in_process_server) |
| : XdgPopupManagerBase{in_process_server}, |
| xdg_shell_surface{client, surface}, |
| toplevel{xdg_shell_surface} |
| { |
| wait_for_frame_to_render(); |
| } |
| |
| void dispatch_until_popup_configure() override |
| { |
| client.dispatch_until( |
| [prev_count = popup_surface_configure_count, ¤t_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(), xdg_shell_surface, 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()); |
| } |
| |
| popup.value().add_close_notification([this](){ popup_done(); }); |
| popup_xdg_surface.value().add_configure_notification([&](uint32_t serial) |
| { |
| zxdg_surface_v6_ack_configure(popup_xdg_surface.value(), serial); |
| popup_surface_configure_count++; |
| }); |
| |
| popup.value().add_configure_notification([this](int32_t x, int32_t y, int32_t width, int32_t height) |
| { |
| state = State{x, y, width, height}; |
| }); |
| } |
| |
| void clear_popup() override |
| { |
| popup = std::experimental::nullopt; |
| popup_xdg_surface = std::experimental::nullopt; |
| } |
| |
| auto popup_position() const -> std::experimental::optional<std::pair<int, int>> override |
| { |
| return std::make_pair(state.value().x, state.value().y); |
| } |
| |
| wlcs::XdgSurfaceV6 xdg_shell_surface; |
| wlcs::XdgToplevelV6 toplevel; |
| |
| std::experimental::optional<wlcs::XdgSurfaceV6> popup_xdg_surface; |
| std::experimental::optional<wlcs::XdgPopupV6> popup; |
| |
| int popup_surface_configure_count{0}; |
| }; |
| |
| class LayerV1PopupManager : public XdgPopupManagerBase |
| { |
| public: |
| LayerV1PopupManager(wlcs::InProcessServer* const in_process_server) |
| : XdgPopupManagerBase{in_process_server}, |
| layer_surface{client, surface} |
| { |
| zwlr_layer_surface_v1_set_size(layer_surface, window_width, window_height); |
| wait_for_frame_to_render(); |
| } |
| |
| void dispatch_until_popup_configure() override |
| { |
| client.dispatch_until( |
| [prev_count = popup_surface_configure_count, ¤t_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::experimental::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()); |
| } |
| |
| popup_xdg_surface.value().add_configure_notification([&](uint32_t serial) |
| { |
| xdg_surface_ack_configure(popup_xdg_surface.value(), serial); |
| popup_surface_configure_count++; |
| }); |
| |
| popup.value().add_close_notification([this](){ popup_done(); }); |
| popup.value().add_configure_notification([this](int32_t x, int32_t y, int32_t width, int32_t height) |
| { |
| state = State{x, y, width, height}; |
| }); |
| } |
| |
| void clear_popup() override |
| { |
| popup = std::experimental::nullopt; |
| popup_xdg_surface = std::experimental::nullopt; |
| } |
| |
| auto popup_position() const -> std::experimental::optional<std::pair<int, int>> override |
| { |
| return std::make_pair(state.value().x, state.value().y); |
| } |
| |
| wlcs::LayerSurfaceV1 layer_surface; |
| |
| std::experimental::optional<wlcs::XdgSurfaceStable> popup_xdg_surface; |
| std::experimental::optional<wlcs::XdgPopupStable> popup; |
| |
| int popup_surface_configure_count{0}; |
| }; |
| |
| } |
| |
| class XdgPopupPositionerTest: |
| public wlcs::StartedInProcessServer, |
| public testing::WithParamInterface<PositionerTestParams> |
| { |
| }; |
| |
| TEST_P(XdgPopupPositionerTest, xdg_shell_stable_popup_placed_correctly) |
| { |
| auto manager = std::make_unique<XdgPopupStableManager>(this); |
| auto const& param = GetParam(); |
| |
| manager->map_popup(param.positioner); |
| |
| ASSERT_THAT( |
| manager->popup_position(), |
| Ne(std::experimental::nullopt)) << "popup configure event not sent"; |
| |
| ASSERT_THAT( |
| manager->popup_position(), |
| Eq(std::experimental::make_optional(param.expected_positon))) << "popup placed in incorrect position"; |
| } |
| |
| TEST_P(XdgPopupPositionerTest, xdg_shell_unstable_v6_popup_placed_correctly) |
| { |
| auto manager = std::make_unique<XdgPopupV6Manager>(this); |
| auto const& param = GetParam(); |
| |
| manager->map_popup(param.positioner); |
| |
| ASSERT_THAT( |
| manager->popup_position(), |
| Ne(std::experimental::nullopt)) << "popup configure event not sent"; |
| |
| ASSERT_THAT( |
| manager->popup_position(), |
| Eq(std::experimental::make_optional(param.expected_positon))) << "popup placed in incorrect position"; |
| } |
| |
| TEST_P(XdgPopupPositionerTest, layer_shell_popup_placed_correctly) |
| { |
| auto manager = std::make_unique<LayerV1PopupManager>(this); |
| auto const& param = GetParam(); |
| |
| manager->map_popup(param.positioner); |
| |
| ASSERT_THAT( |
| manager->popup_position(), |
| Ne(std::experimental::nullopt)) << "popup configure event not sent"; |
| |
| ASSERT_THAT( |
| manager->popup_position(), |
| Eq(std::experimental::make_optional(param.expected_positon))) << "popup placed in incorrect position"; |
| } |
| |
| 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)} |
| )); |
| |
| 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_popup_does_not_get_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(), |
| 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, 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>(this); |
| |
| auto positioner = PositionerParams{} |
| .with_anchor_rect(window_width / 2, window_height / 2, 0, 0); |
| |
| manager->map_popup(positioner); |
| manager->client.roundtrip(); |
| |
| ASSERT_THAT( |
| manager->popup_position(), |
| Eq(std::experimental::make_optional( |
| 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::experimental::nullopt)); |
| EXPECT_THAT(manager->state.value().width, Gt(0)); |
| EXPECT_THAT(manager->state.value().height, Gt(0)); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| XdgPopupStable, |
| XdgPopupTest, |
| testing::Values(XdgPopupTestParam{ |
| [](wlcs::InProcessServer* const server) |
| { |
| return std::make_unique<XdgPopupStableManager>(server); |
| }})); |
| |
| INSTANTIATE_TEST_SUITE_P( |
| XdgPopupUnstableV6, |
| XdgPopupTest, |
| testing::Values(XdgPopupTestParam{ |
| [](wlcs::InProcessServer* const server) |
| { |
| return std::make_unique<XdgPopupV6Manager>(server); |
| }})); |
| |
| INSTANTIATE_TEST_SUITE_P( |
| LayerShellPopup, |
| XdgPopupTest, |
| testing::Values(XdgPopupTestParam{ |
| [](wlcs::InProcessServer* const server) |
| { |
| return std::make_unique<LayerV1PopupManager>(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_constraint_adjustment |
| // TODO: test set_offset |
| // TODO: test that a zero size anchor rect fails on v6 |