blob: 02b509cc54edf138cdaa4d5f92207d284bd1fd59 [file] [edit]
/*
* Copyright © 2024 Canonical Ltd.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "in_process_server.h"
#include "expect_protocol_error.h"
#include <gmock/gmock.h>
using namespace testing;
// NOTE: WLCS currently has no way to observe the compositor's applied cursor
// (its image, hotspot, or whether it is hidden). The "acceptance" tests below
// can therefore only verify that a set_cursor request is not rejected with a
// protocol error; they cannot confirm the request actually took effect. If a
// hook to inspect the applied cursor is added in the future, these tests should
// be extended to assert on the resulting cursor state.
namespace
{
class WlPointerTest : public wlcs::StartedInProcessServer
{
public:
WlPointerTest()
: client{the_server()},
pointer{the_server().create_pointer()},
surface{client.create_visible_surface(surface_width, surface_height)}
{
the_server().move_surface_to(surface, surface_x, surface_y);
}
/// Moves the pointer onto the test surface and returns the serial of the
/// wl_pointer.enter event this produces. Many wl_pointer requests, most
/// notably set_cursor, must be issued with a valid enter serial.
auto move_pointer_to_surface() -> uint32_t
{
return client.move_pointer_to(
pointer, surface_x + pointer_offset_x, surface_y + pointer_offset_y);
}
/// A role-less surface suitable for use as a cursor.
auto make_cursor_surface() -> wlcs::Surface
{
wlcs::Surface cursor{client};
cursor.attach_buffer(cursor_size, cursor_size);
return cursor;
}
wlcs::Client client;
wlcs::Pointer pointer;
wlcs::Surface surface;
static int const surface_width = 200;
static int const surface_height = 200;
static int const surface_x = 100;
static int const surface_y = 100;
static int const pointer_offset_x = 50;
static int const pointer_offset_y = 50;
static int const cursor_size = 24;
static int const cursor_hotspot_x = 4;
static int const cursor_hotspot_y = 4;
};
}
TEST_F(WlPointerTest, set_cursor_with_a_role_less_surface_is_accepted)
{
auto const enter_serial = move_pointer_to_surface();
auto cursor = make_cursor_surface();
// We can only check that the request is accepted; we cannot verify the
// cursor image the compositor ends up showing. See the note at the top of
// this file.
wl_pointer_set_cursor(
client.the_pointer(), enter_serial, cursor, cursor_hotspot_x, cursor_hotspot_y);
wl_surface_commit(cursor);
client.roundtrip();
}
TEST_F(WlPointerTest, set_cursor_with_null_surface_hides_the_cursor)
{
auto const enter_serial = move_pointer_to_surface();
// A null surface asks the compositor to hide the cursor. We can only check
// that the request is accepted; we have no hook to confirm the cursor is
// actually hidden. See the note at the top of this file.
wl_pointer_set_cursor(client.the_pointer(), enter_serial, nullptr, 0, 0);
client.roundtrip();
}
TEST_F(WlPointerTest, set_cursor_with_null_surface_hides_a_previously_set_cursor)
{
auto const enter_serial = move_pointer_to_surface();
auto cursor = make_cursor_surface();
// As above, we can only check that hiding an already-set cursor is
// accepted, not that the cursor image is actually removed. See the note at
// the top of this file.
wl_pointer_set_cursor(
client.the_pointer(), enter_serial, cursor, cursor_hotspot_x, cursor_hotspot_y);
wl_surface_commit(cursor);
wl_pointer_set_cursor(client.the_pointer(), enter_serial, nullptr, 0, 0);
client.roundtrip();
}
TEST_F(WlPointerTest, set_cursor_may_reassign_the_cursor_role_to_the_same_surface)
{
auto const enter_serial = move_pointer_to_surface();
auto cursor = make_cursor_surface();
// The Wayland spec explicitly allows giving a surface the cursor role
// again; this must not raise a protocol error. We can only check that the
// reassignment is accepted, not that the updated hotspot takes effect. See
// the note at the top of this file.
wl_pointer_set_cursor(
client.the_pointer(), enter_serial, cursor, cursor_hotspot_x, cursor_hotspot_y);
wl_surface_commit(cursor);
wl_pointer_set_cursor(
client.the_pointer(), enter_serial, cursor, cursor_hotspot_x + 1, cursor_hotspot_y + 1);
wl_surface_commit(cursor);
client.roundtrip();
}
TEST_F(WlPointerTest, set_cursor_on_a_surface_with_another_role_is_a_protocol_error)
{
auto const enter_serial = move_pointer_to_surface();
// create_visible_surface gives the surface a shell role (wl_shell or
// xdg_toplevel, depending on what the compositor supports).
auto roled_surface = client.create_visible_surface(surface_width, surface_height);
wl_pointer_set_cursor(
client.the_pointer(), enter_serial, roled_surface, cursor_hotspot_x, cursor_hotspot_y);
EXPECT_PROTOCOL_ERROR(
{ client.roundtrip(); },
&wl_pointer_interface, WL_POINTER_ERROR_ROLE);
}
TEST_F(WlPointerTest, set_cursor_on_a_subsurface_is_a_protocol_error)
{
auto const enter_serial = move_pointer_to_surface();
// Creating a subsurface gives the wl_surface the subsurface role.
wlcs::Subsurface subsurface{surface};
wl_pointer_set_cursor(
client.the_pointer(), enter_serial, subsurface, cursor_hotspot_x, cursor_hotspot_y);
EXPECT_PROTOCOL_ERROR(
{ client.roundtrip(); },
&wl_pointer_interface, WL_POINTER_ERROR_ROLE);
}
TEST_F(WlPointerTest, set_cursor_with_a_stale_serial_is_ignored)
{
auto const enter_serial = move_pointer_to_surface();
// create_visible_surface gives the surface a shell role, so it would be
// rejected if the serial were honoured. The spec requires the request to
// be ignored when the serial does not match the latest enter, so no
// protocol error must be raised. We rely on the absence of a protocol
// error here; without a hook to inspect the applied cursor we cannot
// otherwise observe that the request was dropped.
auto roled_surface = client.create_visible_surface(surface_width, surface_height);
wl_pointer_set_cursor(
client.the_pointer(), enter_serial + 1, roled_surface, cursor_hotspot_x, cursor_hotspot_y);
client.roundtrip();
}
TEST_F(WlPointerTest, a_cursor_surface_cannot_be_given_a_different_role)
{
if (!client.xdg_shell_stable())
GTEST_SKIP() << "Compositor does not support xdg_wm_base";
auto const enter_serial = move_pointer_to_surface();
auto cursor = make_cursor_surface();
wl_pointer_set_cursor(
client.the_pointer(), enter_serial, cursor, cursor_hotspot_x, cursor_hotspot_y);
wl_surface_commit(cursor);
client.roundtrip();
// The surface now has the cursor role, so trying to give it the xdg
// surface role must fail.
xdg_wm_base_get_xdg_surface(client.xdg_shell_stable(), cursor);
EXPECT_PROTOCOL_ERROR(
{ client.roundtrip(); },
&xdg_wm_base_interface, XDG_WM_BASE_ERROR_ROLE);
}