[Linux] Add unit tests for xcb mouse input
Signed-off-by: Chris Burel <burelc@amazon.com>monroegm-disable-blank-issue-2
parent
7e67064ef8
commit
43c83f13c6
@ -0,0 +1,401 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <gmock/gmock.h>
|
||||
|
||||
#include <xcb/xcb.h>
|
||||
|
||||
#include <AzFramework/Input/Buses/Requests/InputSystemCursorRequestBus.h>
|
||||
#include <AzFramework/Input/Devices/Mouse/InputDeviceMouse.h>
|
||||
|
||||
#include "XcbBaseTestFixture.h"
|
||||
#include "XcbTestApplication.h"
|
||||
#include "Matchers.h"
|
||||
#include "Actions.h"
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
// Sets up default behavior for mock keyboard responses to xcb methods
|
||||
class XcbInputDeviceMouseTests
|
||||
: public XcbBaseTestFixture
|
||||
{
|
||||
public:
|
||||
void SetUp() override
|
||||
{
|
||||
using testing::Return;
|
||||
using testing::_;
|
||||
|
||||
XcbBaseTestFixture::SetUp();
|
||||
|
||||
ON_CALL(m_interface, xcb_get_setup(&m_connection))
|
||||
.WillByDefault(Return(&s_xcbSetup));
|
||||
ON_CALL(m_interface, xcb_setup_roots_iterator(&s_xcbSetup))
|
||||
.WillByDefault(Return(xcb_screen_iterator_t{&s_xcbScreen}));
|
||||
|
||||
ON_CALL(m_interface, xcb_get_extension_data(&m_connection, &xcb_xfixes_id))
|
||||
.WillByDefault(Return(&s_xfixesExtensionReply));
|
||||
ON_CALL(m_interface, xcb_xfixes_query_version_reply(&m_connection, _, _))
|
||||
.WillByDefault(ReturnMalloc<xcb_xfixes_query_version_reply_t>(
|
||||
/*response_type=*/(uint8_t)XCB_XFIXES_QUERY_VERSION,
|
||||
/*pad0=*/(uint8_t)0,
|
||||
/*sequence=*/(uint16_t)1,
|
||||
/*length=*/0u,
|
||||
/*major_version=*/5u,
|
||||
/*minor_version=*/0u
|
||||
));
|
||||
|
||||
ON_CALL(m_interface, xcb_get_extension_data(&m_connection, &xcb_input_id))
|
||||
.WillByDefault(Return(&s_xfixesExtensionReply));
|
||||
ON_CALL(m_interface, xcb_input_xi_query_version_reply(&m_connection, _, _))
|
||||
.WillByDefault(ReturnMalloc<xcb_input_xi_query_version_reply_t>(
|
||||
/*response_type=*/(uint8_t)XCB_INPUT_XI_QUERY_VERSION,
|
||||
/*pad0=*/(uint8_t)0,
|
||||
/*sequence=*/(uint16_t)1,
|
||||
/*length=*/0u,
|
||||
/*major_version=*/(uint16_t)2,
|
||||
/*minor_version=*/(uint16_t)2
|
||||
));
|
||||
}
|
||||
|
||||
void PumpApplication()
|
||||
{
|
||||
m_application.PumpSystemEventLoopUntilEmpty();
|
||||
m_application.TickSystem();
|
||||
m_application.Tick();
|
||||
}
|
||||
|
||||
protected:
|
||||
static constexpr inline uint8_t s_xinputMajorOpcode = 131;
|
||||
static constexpr inline xcb_window_t s_rootWindow = 1;
|
||||
static constexpr inline xcb_input_device_id_t s_virtualCorePointerId = 2;
|
||||
static constexpr inline xcb_input_device_id_t s_physicalPointerDeviceId = 3;
|
||||
static constexpr inline uint16_t s_screenWidthInPixels = 3840;
|
||||
static constexpr inline uint16_t s_screenHeightInPixels = 2160;
|
||||
static constexpr inline xcb_setup_t s_xcbSetup{
|
||||
/*.status=*/1,
|
||||
/*.pad0=*/0,
|
||||
/*.protocol_major_version=*/11,
|
||||
/*.protocol_minor_version=*/0,
|
||||
};
|
||||
static inline xcb_screen_t s_xcbScreen{
|
||||
/*.root=*/s_rootWindow,
|
||||
/*.default_colormap=*/32,
|
||||
/*.white_pixel=*/16777215,
|
||||
/*.black_pixel=*/0,
|
||||
/*.current_input_masks=*/0,
|
||||
/*.width_in_pixels=*/s_screenWidthInPixels,
|
||||
/*.height_in_pixels=*/s_screenHeightInPixels,
|
||||
/*.width_in_millimeters=*/602,
|
||||
/*.height_in_millimeters=*/341,
|
||||
};
|
||||
static constexpr inline xcb_query_extension_reply_t s_xfixesExtensionReply{
|
||||
/*.response_type=*/XCB_QUERY_EXTENSION,
|
||||
/*.pad0=*/0,
|
||||
/*.sequence=*/1,
|
||||
/*.length=*/0,
|
||||
/*.present=*/1,
|
||||
};
|
||||
static constexpr inline xcb_query_extension_reply_t s_xinputExtensionReply{
|
||||
/*.response_type=*/XCB_QUERY_EXTENSION,
|
||||
/*.pad0=*/0,
|
||||
/*.sequence=*/1,
|
||||
/*.length=*/0,
|
||||
/*.present=*/1,
|
||||
/*.major_opcode=*/s_xinputMajorOpcode,
|
||||
};
|
||||
XcbTestApplication m_application{
|
||||
/*enabledGamepadsCount=*/0,
|
||||
/*keyboardEnabled=*/false,
|
||||
/*motionEnabled=*/false,
|
||||
/*mouseEnabled=*/true,
|
||||
/*touchEnabled=*/false,
|
||||
/*virtualKeyboardEnabled=*/false
|
||||
};
|
||||
};
|
||||
|
||||
struct MouseButtonTestData
|
||||
{
|
||||
xcb_button_index_t m_button;
|
||||
};
|
||||
|
||||
class XcbInputDeviceMouseButtonTests
|
||||
: public XcbInputDeviceMouseTests
|
||||
, public testing::WithParamInterface<MouseButtonTestData>
|
||||
{
|
||||
public:
|
||||
static InputChannelId GetInputChannelIdForButton(const xcb_button_index_t button)
|
||||
{
|
||||
switch (button)
|
||||
{
|
||||
case XCB_BUTTON_INDEX_1:
|
||||
return InputDeviceMouse::Button::Left;
|
||||
case XCB_BUTTON_INDEX_2:
|
||||
return InputDeviceMouse::Button::Right;
|
||||
case XCB_BUTTON_INDEX_3:
|
||||
return InputDeviceMouse::Button::Middle;
|
||||
}
|
||||
return InputChannelId{};
|
||||
}
|
||||
|
||||
AZStd::array<InputChannelId, 4> GetIdleChannelIdsForButton(const xcb_button_index_t button)
|
||||
{
|
||||
switch (button)
|
||||
{
|
||||
case XCB_BUTTON_INDEX_1:
|
||||
return { InputDeviceMouse::Button::Right, InputDeviceMouse::Button::Middle, InputDeviceMouse::Button::Other1, InputDeviceMouse::Button::Other2 };
|
||||
case XCB_BUTTON_INDEX_2:
|
||||
return { InputDeviceMouse::Button::Left, InputDeviceMouse::Button::Middle, InputDeviceMouse::Button::Other1, InputDeviceMouse::Button::Other2 };
|
||||
case XCB_BUTTON_INDEX_3:
|
||||
return { InputDeviceMouse::Button::Left, InputDeviceMouse::Button::Right, InputDeviceMouse::Button::Other1, InputDeviceMouse::Button::Other2 };
|
||||
case XCB_BUTTON_INDEX_4:
|
||||
return { InputDeviceMouse::Button::Left, InputDeviceMouse::Button::Right, InputDeviceMouse::Button::Middle, InputDeviceMouse::Button::Other2 };
|
||||
case XCB_BUTTON_INDEX_5:
|
||||
return { InputDeviceMouse::Button::Left, InputDeviceMouse::Button::Right, InputDeviceMouse::Button::Middle, InputDeviceMouse::Button::Other1 };
|
||||
}
|
||||
return AZStd::array<InputChannelId, 4>();
|
||||
}
|
||||
};
|
||||
|
||||
TEST_P(XcbInputDeviceMouseButtonTests, ButtonInputChannelsUpdateStateFromXcbEvents)
|
||||
{
|
||||
using testing::Each;
|
||||
using testing::Eq;
|
||||
using testing::NotNull;
|
||||
using testing::Property;
|
||||
using testing::Return;
|
||||
|
||||
// Set the expectations for the events that will be generated
|
||||
// nullptr entries represent when the event queue is empty, and will cause
|
||||
// PumpSystemEventLoopUntilEmpty to return
|
||||
//
|
||||
// Event pointers are freed by the calling code, so these actions
|
||||
// malloc new copies
|
||||
//
|
||||
// The xcb mouse does not react to the `XCB_BUTTON_PRESS` /
|
||||
// `XCB_BUTTON_RELEASE` events, but it will still receive those events
|
||||
// from the X server.
|
||||
EXPECT_CALL(m_interface, xcb_poll_for_event(&m_connection))
|
||||
.WillOnce(ReturnMalloc<xcb_generic_event_t>(MakeEvent(xcb_input_raw_button_press_event_t{
|
||||
/*response_type=*/XCB_GE_GENERIC,
|
||||
/*extension=*/s_xinputMajorOpcode,
|
||||
/*sequence=*/4,
|
||||
/*length=*/2,
|
||||
/*event_type=*/XCB_INPUT_RAW_BUTTON_PRESS,
|
||||
/*deviceid=*/s_virtualCorePointerId,
|
||||
/*time=*/3984920,
|
||||
/*detail=*/GetParam().m_button,
|
||||
/*sourceid=*/s_physicalPointerDeviceId,
|
||||
/*valuators_len=*/2,
|
||||
/*flags=*/0,
|
||||
/*pad0[4]=*/{},
|
||||
/*full_sequence=*/4
|
||||
})))
|
||||
.WillOnce(Return(nullptr))
|
||||
.WillOnce(ReturnMalloc<xcb_generic_event_t>(MakeEvent(xcb_button_press_event_t{
|
||||
/*response_type=*/XCB_BUTTON_PRESS,
|
||||
/*detail=*/static_cast<xcb_button_t>(GetParam().m_button),
|
||||
/*sequence=*/4,
|
||||
/*time=*/3984920,
|
||||
/*root=*/s_rootWindow,
|
||||
/*event=*/119537664,
|
||||
/*child=*/0,
|
||||
/*root_x=*/55,
|
||||
/*root_y=*/1099,
|
||||
/*event_x=*/55,
|
||||
/*event_y=*/55,
|
||||
/*state=*/0,
|
||||
/*same_screen=*/1
|
||||
})))
|
||||
.WillOnce(Return(nullptr))
|
||||
.WillOnce(ReturnMalloc<xcb_generic_event_t>(MakeEvent(xcb_input_raw_button_release_event_t{
|
||||
/*response_type=*/XCB_GE_GENERIC,
|
||||
/*extension=*/s_xinputMajorOpcode,
|
||||
/*sequence=*/4,
|
||||
/*length=*/2,
|
||||
/*event_type=*/XCB_INPUT_RAW_BUTTON_RELEASE,
|
||||
/*deviceid=*/s_virtualCorePointerId,
|
||||
/*time=*/3984964,
|
||||
/*detail=*/GetParam().m_button,
|
||||
/*sourceid=*/s_physicalPointerDeviceId,
|
||||
/*valuators_len=*/2,
|
||||
/*flags=*/0,
|
||||
/*pad0[4]=*/{},
|
||||
/*full_sequence=*/4
|
||||
})))
|
||||
.WillOnce(Return(nullptr))
|
||||
.WillOnce(ReturnMalloc<xcb_generic_event_t>(MakeEvent(xcb_button_release_event_t{
|
||||
/*response_type=*/XCB_BUTTON_RELEASE,
|
||||
/*detail=*/static_cast<xcb_button_t>(GetParam().m_button),
|
||||
/*sequence=*/4,
|
||||
/*time=*/3984964,
|
||||
/*root=*/s_rootWindow,
|
||||
/*event=*/119537664,
|
||||
/*child=*/0,
|
||||
/*root_x=*/55,
|
||||
/*root_y=*/1099,
|
||||
/*event_x=*/55,
|
||||
/*event_y=*/55,
|
||||
/*state=*/XCB_KEY_BUT_MASK_BUTTON_1,
|
||||
/*same_screen=*/1
|
||||
})))
|
||||
.WillOnce(Return(nullptr))
|
||||
;
|
||||
|
||||
m_application.Start();
|
||||
InputSystemCursorRequestBus::Event(
|
||||
InputDeviceMouse::Id,
|
||||
&InputSystemCursorRequests::SetSystemCursorState,
|
||||
SystemCursorState::ConstrainedAndHidden);
|
||||
|
||||
const InputChannel* activeButtonChannel = InputChannelRequests::FindInputChannel(GetInputChannelIdForButton(GetParam().m_button));
|
||||
const auto inactiveButtonChannels = [this]()
|
||||
{
|
||||
const auto inactiveButtonChannelIds = GetIdleChannelIdsForButton(GetParam().m_button);
|
||||
AZStd::array<const InputChannel*, 4> channels{};
|
||||
AZStd::transform(begin(inactiveButtonChannelIds), end(inactiveButtonChannelIds), begin(channels), [](const InputChannelId& id)
|
||||
{
|
||||
return InputChannelRequests::FindInputChannel(id);
|
||||
});
|
||||
return channels;
|
||||
}();
|
||||
|
||||
ASSERT_TRUE(activeButtonChannel);
|
||||
ASSERT_THAT(inactiveButtonChannels, Each(NotNull()));
|
||||
|
||||
EXPECT_THAT(activeButtonChannel->GetState(), Eq(InputChannel::State::Idle));
|
||||
EXPECT_THAT(inactiveButtonChannels, Each(Property(&InputChannel::GetState, Eq(InputChannel::State::Idle))));
|
||||
|
||||
PumpApplication();
|
||||
|
||||
EXPECT_THAT(activeButtonChannel->GetState(), Eq(InputChannel::State::Began));
|
||||
EXPECT_THAT(inactiveButtonChannels, Each(Property(&InputChannel::GetState, Eq(InputChannel::State::Idle))));
|
||||
|
||||
PumpApplication();
|
||||
|
||||
EXPECT_THAT(activeButtonChannel->GetState(), Eq(InputChannel::State::Updated));
|
||||
EXPECT_THAT(inactiveButtonChannels, Each(Property(&InputChannel::GetState, Eq(InputChannel::State::Idle))));
|
||||
|
||||
PumpApplication();
|
||||
|
||||
EXPECT_THAT(activeButtonChannel->GetState(), Eq(InputChannel::State::Ended));
|
||||
EXPECT_THAT(inactiveButtonChannels, Each(Property(&InputChannel::GetState, Eq(InputChannel::State::Idle))));
|
||||
|
||||
PumpApplication();
|
||||
|
||||
EXPECT_THAT(activeButtonChannel->GetState(), Eq(InputChannel::State::Idle));
|
||||
EXPECT_THAT(inactiveButtonChannels, Each(Property(&InputChannel::GetState, Eq(InputChannel::State::Idle))));
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
AllButtons,
|
||||
XcbInputDeviceMouseButtonTests,
|
||||
testing::Values(
|
||||
MouseButtonTestData{ XCB_BUTTON_INDEX_1 },
|
||||
MouseButtonTestData{ XCB_BUTTON_INDEX_2 },
|
||||
MouseButtonTestData{ XCB_BUTTON_INDEX_3 }
|
||||
// XCB_BUTTON_INDEX_4 and XCB_BUTTON_INDEX_5 map to positive and
|
||||
// negative scroll wheel events, which are handled as motion events
|
||||
)
|
||||
);
|
||||
|
||||
TEST_F(XcbInputDeviceMouseTests, MovementInputChannelsUpdateStateFromXcbEvents)
|
||||
{
|
||||
using testing::Each;
|
||||
using testing::Eq;
|
||||
using testing::FloatEq;
|
||||
using testing::NotNull;
|
||||
using testing::Property;
|
||||
using testing::Return;
|
||||
|
||||
// Set the expectations for the events that will be generated
|
||||
// nullptr entries represent when the event queue is empty, and will cause
|
||||
// PumpSystemEventLoopUntilEmpty to return
|
||||
//
|
||||
// Event pointers are freed by the calling code, so these actions
|
||||
// malloc new copies
|
||||
//
|
||||
// The xcb mouse does not react to the `XCB_MOTION_NOTIFY` event, but
|
||||
// it will still receive it from the X server.
|
||||
EXPECT_CALL(m_interface, xcb_poll_for_event(&m_connection))
|
||||
.WillOnce(ReturnMalloc<xcb_generic_event_t>(MakeEvent(xcb_input_raw_motion_event_t{
|
||||
/*response_type=*/XCB_GE_GENERIC,
|
||||
/*extension=*/s_xinputMajorOpcode,
|
||||
/*sequence=*/5,
|
||||
/*length=*/10,
|
||||
/*event_type=*/XCB_INPUT_RAW_MOTION,
|
||||
/*deviceid=*/s_virtualCorePointerId,
|
||||
/*time=*/0, // use the time value to identify each event
|
||||
/*detail=*/XCB_MOTION_NORMAL,
|
||||
/*sourceid=*/s_physicalPointerDeviceId,
|
||||
/*valuators_len=*/2, // number of axes that have values for this event
|
||||
/*flags=*/0,
|
||||
/*pad0[4]=*/{},
|
||||
/*full_sequence=*/5,
|
||||
})))
|
||||
.WillOnce(Return(nullptr))
|
||||
.WillOnce(ReturnMalloc<xcb_generic_event_t>(MakeEvent(xcb_motion_notify_event_t{
|
||||
/*response_type=*/XCB_MOTION_NOTIFY,
|
||||
/*detail=*/XCB_MOTION_NORMAL,
|
||||
/*sequence=*/5,
|
||||
/*time=*/1, // use the time value to identify each event
|
||||
/*root=*/s_rootWindow,
|
||||
/*event=*/127926272,
|
||||
/*child=*/0,
|
||||
/*root_x=*/95,
|
||||
/*root_y=*/1079,
|
||||
/*event_x=*/95,
|
||||
/*event_y=*/20,
|
||||
/*state=*/0,
|
||||
/*same_screen=*/1,
|
||||
})))
|
||||
.WillOnce(Return(nullptr))
|
||||
;
|
||||
|
||||
AZStd::array axisValues
|
||||
{
|
||||
xcb_input_fp3232_t{ /*.integral=*/ 1, /*.fraction=*/0 }, // x motion
|
||||
xcb_input_fp3232_t{ /*.integral=*/ 2, /*.fraction=*/0 } // y motion
|
||||
};
|
||||
|
||||
EXPECT_CALL(m_interface, xcb_input_raw_button_press_axisvalues_length(testing::Field(&xcb_input_raw_button_press_event_t::time, 0)))
|
||||
.WillRepeatedly(testing::Return(2)); // x and y axis
|
||||
EXPECT_CALL(m_interface, xcb_input_raw_button_press_axisvalues_raw(testing::Field(&xcb_input_raw_button_press_event_t::time, 0)))
|
||||
.WillRepeatedly(testing::Return(axisValues.data())); // x and y axis
|
||||
|
||||
m_application.Start();
|
||||
InputSystemCursorRequestBus::Event(
|
||||
InputDeviceMouse::Id,
|
||||
&InputSystemCursorRequests::SetSystemCursorState,
|
||||
SystemCursorState::ConstrainedAndHidden);
|
||||
|
||||
const InputChannel* xMotionChannel = InputChannelRequests::FindInputChannel(InputDeviceMouse::Movement::X);
|
||||
const InputChannel* yMotionChannel = InputChannelRequests::FindInputChannel(InputDeviceMouse::Movement::Y);
|
||||
ASSERT_TRUE(xMotionChannel);
|
||||
ASSERT_TRUE(yMotionChannel);
|
||||
|
||||
EXPECT_THAT(xMotionChannel->GetState(), Eq(InputChannel::State::Idle));
|
||||
EXPECT_THAT(yMotionChannel->GetState(), Eq(InputChannel::State::Idle));
|
||||
EXPECT_THAT(xMotionChannel->GetValue(), FloatEq(0.0f));
|
||||
EXPECT_THAT(yMotionChannel->GetValue(), FloatEq(0.0f));
|
||||
|
||||
PumpApplication();
|
||||
|
||||
EXPECT_THAT(xMotionChannel->GetState(), Eq(InputChannel::State::Began));
|
||||
EXPECT_THAT(yMotionChannel->GetState(), Eq(InputChannel::State::Began));
|
||||
EXPECT_THAT(xMotionChannel->GetValue(), FloatEq(1.0f));
|
||||
EXPECT_THAT(yMotionChannel->GetValue(), FloatEq(2.0f));
|
||||
|
||||
PumpApplication();
|
||||
|
||||
EXPECT_THAT(xMotionChannel->GetState(), Eq(InputChannel::State::Ended));
|
||||
EXPECT_THAT(yMotionChannel->GetState(), Eq(InputChannel::State::Ended));
|
||||
EXPECT_THAT(xMotionChannel->GetValue(), FloatEq(0.0f));
|
||||
EXPECT_THAT(yMotionChannel->GetValue(), FloatEq(0.0f));
|
||||
}
|
||||
} // namespace AzFramework
|
||||
Loading…
Reference in New Issue