| /* |
| * Copyright © 2012 Intel Corporation |
| * Copyright © 2013 Collabora, Ltd. |
| * Copyright © 2017 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 <deque> |
| #include <tuple> |
| |
| #include <gmock/gmock.h> |
| |
| using ClientSurfaceEventsTest = wlcs::InProcessServer; |
| |
| // |
| //static void |
| //check_pointer(struct client *client, int x, int y) |
| //{ |
| // int sx, sy; |
| // |
| // /* check that the client got the global pointer update */ |
| // assert(client->test->pointer_x == x); |
| // assert(client->test->pointer_y == y); |
| // |
| // /* Does global pointer map onto the surface? */ |
| // if (surface_contains(client->surface, x, y)) { |
| // /* check that the surface has the pointer focus */ |
| // assert(client->input->pointer->focus == client->surface); |
| // |
| // /* |
| // * check that the local surface pointer maps |
| // * to the global pointer. |
| // */ |
| // sx = client->input->pointer->x + client->surface->x; |
| // sy = client->input->pointer->y + client->surface->y; |
| // assert(sx == x); |
| // assert(sy == y); |
| // } else { |
| // /* |
| // * The global pointer does not map onto surface. So |
| // * check that it doesn't have the pointer focus. |
| // */ |
| // assert(client->input->pointer->focus == NULL); |
| // } |
| //} |
| // |
| //static void |
| //check_pointer_move(struct client *client, int x, int y) |
| //{ |
| // weston_test_move_pointer(client->test->weston_test, x, y); |
| // client_roundtrip(client); |
| // check_pointer(client, x, y); |
| //} |
| // |
| //TEST(test_pointer_surface_move) |
| //{ |
| // struct client *client; |
| // |
| // client = create_client_and_test_surface(100, 100, 100, 100); |
| // assert(client); |
| // |
| // /* move pointer outside of client */ |
| // assert(!surface_contains(client->surface, 50, 50)); |
| // check_pointer_move(client, 50, 50); |
| // |
| // /* move client center to pointer */ |
| // move_client(client, 0, 0); |
| // assert(surface_contains(client->surface, 50, 50)); |
| // check_pointer(client, 50, 50); |
| //} |
| // |
| //static int |
| //output_contains_client(struct client *client) |
| //{ |
| // struct output *output = client->output; |
| // struct surface *surface = client->surface; |
| // |
| // return !(output->x >= surface->x + surface->width |
| // || output->x + output->width <= surface->x |
| // || output->y >= surface->y + surface->height |
| // || output->y + output->height <= surface->y); |
| //} |
| // |
| //static void |
| //check_client_move(struct client *client, int x, int y) |
| //{ |
| // move_client(client, x, y); |
| // |
| // if (output_contains_client(client)) { |
| // assert(client->surface->output == client->output); |
| // } else { |
| // assert(client->surface->output == NULL); |
| // } |
| //} |
| // |
| //TEST(test_surface_output) |
| //{ |
| // struct client *client; |
| // int x, y; |
| // |
| // client = create_client_and_test_surface(100, 100, 100, 100); |
| // assert(client); |
| // |
| // assert(output_contains_client(client)); |
| // |
| // /* not visible */ |
| // x = 0; |
| // y = -client->surface->height; |
| // check_client_move(client, x, y); |
| // |
| // /* visible */ |
| // check_client_move(client, x, ++y); |
| // |
| // /* not visible */ |
| // x = -client->surface->width; |
| // y = 0; |
| // check_client_move(client, x, y); |
| // |
| // /* visible */ |
| // check_client_move(client, ++x, y); |
| // |
| // /* not visible */ |
| // x = client->output->width; |
| // y = 0; |
| // check_client_move(client, x, y); |
| // |
| // /* visible */ |
| // check_client_move(client, --x, y); |
| // assert(output_contains_client(client)); |
| // |
| // /* not visible */ |
| // x = 0; |
| // y = client->output->height; |
| // check_client_move(client, x, y); |
| // assert(!output_contains_client(client)); |
| // |
| // /* visible */ |
| // check_client_move(client, x, --y); |
| // assert(output_contains_client(client)); |
| //} |
| |
| struct PointerMotion |
| { |
| static int constexpr window_width = 231; |
| static int constexpr window_height = 220; |
| std::string name; |
| int initial_x, initial_y; // Relative to surface top-left |
| int dx, dy; |
| }; |
| |
| std::ostream& operator<<(std::ostream& out, PointerMotion const& motion) |
| { |
| return out << motion.name; |
| } |
| |
| class SurfacePointerMotionTest : |
| public wlcs::InProcessServer, |
| public testing::WithParamInterface<PointerMotion> |
| { |
| }; |
| |
| TEST_P(SurfacePointerMotionTest, pointer_movement) |
| { |
| using namespace testing; |
| |
| auto pointer = the_server().create_pointer(); |
| |
| wlcs::Client client{the_server()}; |
| |
| auto const params = GetParam(); |
| |
| auto surface = client.create_visible_surface( |
| params.window_width, |
| params.window_height); |
| |
| int const top_left_x = 23, top_left_y = 231; |
| the_server().move_surface_to(surface, top_left_x, top_left_y); |
| |
| auto const wl_surface = static_cast<struct wl_surface*>(surface); |
| |
| pointer.move_to(top_left_x + params.initial_x, top_left_y + params.initial_y); |
| |
| client.roundtrip(); |
| |
| EXPECT_THAT(client.window_under_cursor(), Ne(wl_surface)); |
| |
| /* move pointer; it should now be inside the surface */ |
| pointer.move_by(params.dx, params.dy); |
| |
| client.roundtrip(); |
| |
| EXPECT_THAT(client.window_under_cursor(), Eq(wl_surface)); |
| EXPECT_THAT(client.pointer_position(), |
| Eq(std::make_pair( |
| wl_fixed_from_int(params.initial_x + params.dx), |
| wl_fixed_from_int(params.initial_y + params.dy)))); |
| |
| /* move pointer back; it should now be outside the surface */ |
| pointer.move_by(-params.dx, -params.dy); |
| |
| client.roundtrip(); |
| EXPECT_THAT(client.window_under_cursor(), Ne(wl_surface)); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| PointerCrossingSurfaceCorner, |
| SurfacePointerMotionTest, |
| testing::Values( |
| PointerMotion{"Top-left", -1, -1, 1, 1}, |
| PointerMotion{"Bottom-left", -1, PointerMotion::window_height, 1, -1}, |
| PointerMotion{"Bottom-right", PointerMotion::window_width, PointerMotion::window_height, -1, -1}, |
| PointerMotion{"Top-right", PointerMotion::window_width, -1, -1, 1} |
| )); |
| |
| INSTANTIATE_TEST_SUITE_P( |
| PointerCrossingSurfaceEdge, |
| SurfacePointerMotionTest, |
| testing::Values( |
| PointerMotion{ |
| "Centre-left", |
| -1, PointerMotion::window_height / 2, |
| 1, 0}, |
| PointerMotion{ |
| "Bottom-centre", |
| PointerMotion::window_width / 2, PointerMotion::window_height, |
| 0, -1}, |
| PointerMotion{ |
| "Centre-right", |
| PointerMotion::window_width, PointerMotion::window_height / 2, |
| -1, 0}, |
| PointerMotion{ |
| "Top-centre", |
| PointerMotion::window_width / 2, -1, |
| 0, 1} |
| )); |
| |
| TEST_F(ClientSurfaceEventsTest, surface_moves_under_pointer) |
| { |
| using namespace testing; |
| |
| auto pointer = the_server().create_pointer(); |
| |
| wlcs::Client client{the_server()}; |
| |
| auto surface = client.create_visible_surface(100, 100); |
| auto const wl_surface = static_cast<struct wl_surface*>(surface); |
| |
| /* Set up the pointer outside the surface */ |
| the_server().move_surface_to(surface, 0, 0); |
| pointer.move_to(500, 500); |
| |
| client.roundtrip(); |
| |
| EXPECT_THAT(client.window_under_cursor(), Ne(wl_surface)); |
| |
| /* move the surface so that it is under the pointer */ |
| the_server().move_surface_to(surface, 450, 450); |
| |
| client.dispatch_until( |
| [wl_surface, &client]() |
| { |
| return client.window_under_cursor() == wl_surface; |
| }); |
| |
| EXPECT_THAT(client.window_under_cursor(), Eq(wl_surface)); |
| EXPECT_THAT(client.pointer_position(), |
| Eq(std::make_pair( |
| wl_fixed_from_int(50), |
| wl_fixed_from_int(50)))); |
| } |
| |
| TEST_F(ClientSurfaceEventsTest, surface_moves_over_surface_under_pointer) |
| { |
| using namespace testing; |
| |
| auto pointer = the_server().create_pointer(); |
| |
| wlcs::Client client{the_server()}; |
| |
| auto first_surface = client.create_visible_surface(100, 100); |
| auto second_surface = client.create_visible_surface(100, 100); |
| |
| /* Set up the pointer outside the surface */ |
| the_server().move_surface_to(first_surface, 0, 0); |
| the_server().move_surface_to(second_surface, 0, 0); |
| pointer.move_to(500, 500); |
| |
| client.roundtrip(); |
| |
| /* move the first surface so that it is under the pointer */ |
| the_server().move_surface_to(first_surface, 450, 450); |
| |
| bool first_surface_focused{false}; |
| client.add_pointer_enter_notification( |
| [&first_surface_focused, &first_surface](wl_surface* surf, auto, auto) |
| { |
| if (surf == first_surface) |
| { |
| first_surface_focused = true; |
| } |
| return false; |
| }); |
| |
| // Wait until the first surface is focused |
| client.dispatch_until( |
| [&first_surface_focused]() |
| { |
| return first_surface_focused; |
| }); |
| |
| client.add_pointer_leave_notification( |
| [&first_surface_focused, &first_surface](wl_surface* surf) |
| { |
| if (surf == first_surface) |
| { |
| first_surface_focused = false; |
| } |
| return false; |
| }); |
| |
| bool second_surface_focused{false}; |
| client.add_pointer_enter_notification( |
| [&first_surface_focused, &second_surface_focused, &second_surface](auto surf, auto x, auto y) |
| { |
| if (surf == second_surface) |
| { |
| /* |
| * Protocol requires that the pointer-leave event is sent before pointer-enter |
| */ |
| EXPECT_FALSE(first_surface_focused); |
| second_surface_focused = true; |
| EXPECT_THAT(x, Eq(wl_fixed_from_int(50))); |
| EXPECT_THAT(y, Eq(wl_fixed_from_int(50))); |
| } |
| return false; |
| }); |
| |
| the_server().move_surface_to(second_surface, 450, 450); |
| |
| client.dispatch_until( |
| [&second_surface_focused]() |
| { |
| return second_surface_focused; |
| }); |
| } |
| |
| TEST_F(ClientSurfaceEventsTest, surface_resizes_under_pointer) |
| { |
| using namespace testing; |
| |
| auto pointer = the_server().create_pointer(); |
| |
| wlcs::Client client{the_server()}; |
| |
| auto surface = client.create_visible_surface(100, 100); |
| |
| /* Set up the pointer outside the surface */ |
| the_server().move_surface_to(surface, 400, 400); |
| pointer.move_to(500, 500); |
| |
| client.roundtrip(); |
| |
| ASSERT_THAT(client.window_under_cursor(), Ne(static_cast<wl_surface*>(surface))); |
| |
| bool surface_entered{false}; |
| client.add_pointer_enter_notification( |
| [&surface_entered, &surface](wl_surface* entered_surf, wl_fixed_t x, wl_fixed_t y) |
| { |
| EXPECT_THAT(surface, Eq(entered_surf)); |
| EXPECT_THAT(x, Eq(wl_fixed_from_int(100))); |
| EXPECT_THAT(y, Eq(wl_fixed_from_int(100))); |
| surface_entered = true; |
| return false; |
| }); |
| client.add_pointer_leave_notification( |
| [&surface_entered, &surface](wl_surface* left_surf) |
| { |
| EXPECT_THAT(surface, Eq(left_surf)); |
| surface_entered = false; |
| return false; |
| }); |
| |
| auto larger_buffer = wlcs::ShmBuffer{client, 200, 200}; |
| auto smaller_buffer = wlcs::ShmBuffer{client, 50, 50}; |
| |
| // Resize the surface so that the pointer is now over the top... |
| wl_surface_attach(surface, larger_buffer, 0, 0); |
| wl_surface_commit(surface); |
| |
| client.dispatch_until( |
| [&surface_entered]() |
| { |
| return surface_entered; |
| }); |
| |
| // Resize the surface so that the pointer is no longer over the top... |
| wl_surface_attach(surface, smaller_buffer, 0, 0); |
| wl_surface_commit(surface); |
| |
| client.dispatch_until( |
| [&surface_entered]() |
| { |
| return !surface_entered; |
| }); |
| } |
| |
| TEST_F(ClientSurfaceEventsTest, surface_moves_while_under_pointer) |
| { |
| using namespace testing; |
| |
| auto pointer = the_server().create_pointer(); |
| |
| wlcs::Client client{the_server()}; |
| |
| auto surface = client.create_visible_surface(100, 100); |
| |
| the_server().move_surface_to(surface, 450, 450); |
| pointer.move_to(500, 500); |
| |
| std::deque<std::pair<int, int>> surface_movements = { |
| std::make_pair(445, 455), |
| std::make_pair(460, 405), |
| std::make_pair(420, 440), |
| std::make_pair(430, 460), |
| std::make_pair(0, 0) // The last motion is not checked for |
| }; |
| |
| client.dispatch_until( |
| [&client, &surface]() |
| { |
| if (client.window_under_cursor() == surface) |
| { |
| EXPECT_THAT(client.pointer_position().first, Eq(wl_fixed_from_int(50))); |
| EXPECT_THAT(client.pointer_position().second, Eq(wl_fixed_from_int(50))); |
| return true; |
| } |
| return false; |
| }); |
| |
| int expected_x{55}, expected_y{45}; |
| client.add_pointer_motion_notification( |
| [this, &surface, &surface_movements, &expected_x, &expected_y] |
| (wl_fixed_t x, wl_fixed_t y) |
| { |
| EXPECT_THAT(x, Eq(wl_fixed_from_int(expected_x))); |
| EXPECT_THAT(y, Eq(wl_fixed_from_int(expected_y))); |
| |
| auto next_movement = surface_movements.front(); |
| surface_movements.pop_front(); |
| |
| the_server().move_surface_to( |
| surface, |
| next_movement.first, |
| next_movement.second); |
| |
| expected_x = 500 - next_movement.first; |
| expected_y = 500 - next_movement.second; |
| return true; |
| }); |
| |
| // Do the initial surface move |
| the_server().move_surface_to( |
| surface, |
| surface_movements.front().first, |
| surface_movements.front().second); |
| surface_movements.pop_front(); |
| |
| client.dispatch_until( |
| [&surface_movements]() |
| { |
| return surface_movements.empty(); |
| }); |
| } |
| |
| TEST_F(ClientSurfaceEventsTest, frame_timestamp_increases) |
| { |
| using namespace testing; |
| |
| wlcs::Client client{the_server()}; |
| |
| auto surface = client.create_visible_surface(100, 100); |
| |
| std::array<wlcs::ShmBuffer, 3> buffers = {{ |
| wlcs::ShmBuffer{client, 100, 100}, |
| wlcs::ShmBuffer{client, 100, 100}, |
| wlcs::ShmBuffer{client, 100, 100} |
| }}; |
| |
| /* |
| * The first buffer must never be released, since it is replaced before |
| * it is committed, therefore it never becomes busy. |
| */ |
| wl_surface_attach(surface, buffers[0], 0, 0); |
| wl_surface_attach(surface, buffers[1], 0, 0); |
| |
| int prev_frame_time = 0; |
| int frame_callback_count = 0; |
| surface.add_frame_callback( |
| [&](int time) |
| { |
| EXPECT_THAT(time, Gt(prev_frame_time)); |
| prev_frame_time = time; |
| frame_callback_count++; |
| }); |
| wl_surface_commit(surface); |
| |
| /** |
| * We need to sleep for multiple miliseconds to make sure the timestamp |
| * really does go up |
| */ |
| usleep(10000); |
| |
| wl_surface_attach(surface, buffers[2], 0, 0); |
| wl_surface_commit(surface); |
| |
| client.dispatch_until([&frame_callback_count]() |
| { |
| return frame_callback_count >= 2; |
| }); |
| } |
| |
| TEST_F(ClientSurfaceEventsTest, buffer_release) |
| { |
| wlcs::Client client{the_server()}; |
| |
| auto surface = client.create_visible_surface(100, 100); |
| |
| std::array<wlcs::ShmBuffer, 3> buffers = {{ |
| wlcs::ShmBuffer{client, 100, 100}, |
| wlcs::ShmBuffer{client, 100, 100}, |
| wlcs::ShmBuffer{client, 100, 100} |
| }}; |
| std::array<bool, 3> buffer_released = {{ |
| false, |
| false, |
| false |
| }}; |
| |
| for (auto i = 0u; i < buffers.size(); ++i) |
| { |
| buffers[i].add_release_listener( |
| [released = &buffer_released[i]]() |
| { |
| *released = true; |
| return false; |
| }); |
| } |
| |
| /* |
| * The first buffer must never be released, since it is replaced before |
| * it is committed, therefore it never becomes busy. |
| */ |
| wl_surface_attach(surface, buffers[0], 0, 0); |
| wl_surface_attach(surface, buffers[1], 0, 0); |
| |
| bool frame_consumed{false}; |
| surface.add_frame_callback( |
| [&frame_consumed](auto) |
| { |
| frame_consumed = true; |
| }); |
| wl_surface_commit(surface); |
| |
| client.dispatch_until( |
| [&frame_consumed]() |
| { |
| return frame_consumed; |
| }); |
| |
| EXPECT_FALSE(buffer_released[0]); |
| // buffers[1] may or may not be released |
| EXPECT_FALSE(buffer_released[2]); |
| |
| wl_surface_attach(surface, buffers[2], 0, 0); |
| |
| frame_consumed = false; |
| surface.add_frame_callback( |
| [&frame_consumed](auto) |
| { |
| frame_consumed = true; |
| }); |
| wl_surface_commit(surface); |
| |
| client.dispatch_until( |
| [&frame_consumed]() |
| { |
| return frame_consumed; |
| }); |
| EXPECT_FALSE(buffer_released[0]); |
| EXPECT_TRUE(buffer_released[1]); |
| // buffer[2] may or may not be released |
| |
| wlcs::ShmBuffer final_buffer(client, 100, 100); |
| wl_surface_attach(surface, final_buffer, 0, 0); |
| |
| frame_consumed = false; |
| surface.add_frame_callback( |
| [&frame_consumed](auto) |
| { |
| frame_consumed = true; |
| }); |
| wl_surface_commit(surface); |
| |
| client.dispatch_until( |
| [&frame_consumed]() |
| { |
| return frame_consumed; |
| }); |
| |
| EXPECT_FALSE(buffer_released[0]); |
| EXPECT_TRUE(buffer_released[1]); |
| EXPECT_TRUE(buffer_released[2]); |
| } |
| |
| TEST_F(ClientSurfaceEventsTest, surface_enters_output) |
| { |
| using namespace testing; |
| |
| wlcs::Client client{the_server()}; |
| |
| auto surface = client.create_visible_surface(100, 100); |
| client.roundtrip(); |
| |
| EXPECT_THAT(surface.current_outputs(), Not(IsEmpty())) << "Surface did not initially enter output"; |
| } |
| |
| // TODO: test surfaces can leave outputs once we can create multiple outputs |
| |
| // TODO: make parameterized for different types of shell surfaces |