Merge branch stabilization/2110 into development

Signed-off-by: Chris Burel <burelc@amazon.com>
monroegm-disable-blank-issue-2
Chris Burel 4 years ago
commit f97fa6dbe0

@ -10,6 +10,8 @@
#ifdef PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB
#include <AzFramework/XcbEventHandler.h>
#include <AzFramework/XcbConnectionManager.h>
#include <qpa/qplatformnativeinterface.h>
#endif
namespace Editor
@ -23,16 +25,34 @@ namespace Editor
return nullptr;
}
xcb_connection_t* EditorQtApplicationXcb::GetXcbConnectionFromQt()
{
QPlatformNativeInterface* native = platformNativeInterface();
AZ_Warning("EditorQtApplicationXcb", native, "Unable to retrieve the native platform interface");
if (!native)
{
return nullptr;
}
return reinterpret_cast<xcb_connection_t*>(native->nativeResourceForIntegration(QByteArray("connection")));
}
void EditorQtApplicationXcb::OnStartPlayInEditor()
{
auto* interface = AzFramework::XcbConnectionManagerInterface::Get();
interface->SetEnableXInput(GetXcbConnectionFromQt(), true);
}
void EditorQtApplicationXcb::OnStopPlayInEditor()
{
auto* interface = AzFramework::XcbConnectionManagerInterface::Get();
interface->SetEnableXInput(GetXcbConnectionFromQt(), false);
}
bool EditorQtApplicationXcb::nativeEventFilter([[maybe_unused]] const QByteArray& eventType, void* message, long*)
{
if (GetIEditor()->IsInGameMode())
{
#ifdef PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB
// We need to handle RAW Input events in a separate loop. This is a workaround to enable XInput2 RAW Inputs using Editor mode.
// TODO To have this call here might be not be perfect.
AzFramework::XcbEventHandlerBus::Broadcast(&AzFramework::XcbEventHandler::PollSpecialEvents);
// Now handle the rest of the events.
AzFramework::XcbEventHandlerBus::Broadcast(
&AzFramework::XcbEventHandler::HandleXcbEvent, static_cast<xcb_generic_event_t*>(message));
#endif

@ -6,19 +6,35 @@
*
*/
#if !defined(Q_MOC_RUN)
#include <Editor/Core/QtEditorApplication.h>
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
#endif
using xcb_connection_t = struct xcb_connection_t;
namespace Editor
{
class EditorQtApplicationXcb : public EditorQtApplication
class EditorQtApplicationXcb
: public EditorQtApplication
, public AzToolsFramework::EditorEntityContextNotificationBus::Handler
{
Q_OBJECT
public:
EditorQtApplicationXcb(int& argc, char** argv)
: EditorQtApplication(argc, argv)
{
// Connect bus to listen for OnStart/StopPlayInEditor events
AzToolsFramework::EditorEntityContextNotificationBus::Handler::BusConnect();
}
xcb_connection_t* GetXcbConnectionFromQt();
///////////////////////////////////////////////////////////////////////
// AzToolsFramework::EditorEntityContextNotificationBus overrides
void OnStartPlayInEditor() override;
void OnStopPlayInEditor() override;
// QAbstractNativeEventFilter:
bool nativeEventFilter(const QByteArray& eventType, void* message, long* result) override;
};

@ -10,6 +10,8 @@
#include <AzFramework/XcbEventHandler.h>
#include <AzFramework/XcbInterface.h>
#include <xcb/xinput.h>
namespace AzFramework
{
////////////////////////////////////////////////////////////////////////////////////////////////
@ -34,6 +36,31 @@ namespace AzFramework
return m_xcbConnection.get();
}
void SetEnableXInput(xcb_connection_t* connection, bool enable) override
{
struct Mask
{
xcb_input_event_mask_t head;
xcb_input_xi_event_mask_t mask;
};
const Mask mask {
/*.head=*/{
/*.device_id=*/XCB_INPUT_DEVICE_ALL_MASTER,
/*.mask_len=*/1
},
/*.mask=*/ enable ?
(xcb_input_xi_event_mask_t)(XCB_INPUT_XI_EVENT_MASK_RAW_MOTION | XCB_INPUT_XI_EVENT_MASK_RAW_BUTTON_PRESS | XCB_INPUT_XI_EVENT_MASK_RAW_BUTTON_RELEASE) :
(xcb_input_xi_event_mask_t)XCB_NONE
};
const xcb_setup_t* xcbSetup = xcb_get_setup(connection);
const xcb_screen_t* xcbScreen = xcb_setup_roots_iterator(xcbSetup).data;
xcb_input_xi_select_events(connection, xcbScreen->root, 1, &mask.head);
xcb_flush(connection);
}
private:
XcbUniquePtr<xcb_connection_t, xcb_disconnect> m_xcbConnection = nullptr;
};

@ -24,6 +24,9 @@ namespace AzFramework
virtual ~XcbConnectionManager() = default;
virtual xcb_connection_t* GetXcbConnection() const = 0;
//! Enables/Disables XInput Raw Input events.
virtual void SetEnableXInput(xcb_connection_t* connection, bool enable) = 0;
};
class XcbConnectionManagerBusTraits

@ -23,9 +23,6 @@ namespace AzFramework
virtual ~XcbEventHandler() = default;
virtual void HandleXcbEvent(xcb_generic_event_t* event) = 0;
// ATTN This is used as a workaround for RAW Input events when using the Editor.
virtual void PollSpecialEvents(){};
};
class XcbEventHandlerBusTraits : public AZ::EBusTraits

@ -13,21 +13,68 @@
namespace AzFramework
{
xcb_window_t GetSystemCursorFocusWindow()
xcb_window_t GetSystemCursorFocusWindow(xcb_connection_t* connection)
{
void* systemCursorFocusWindow = nullptr;
AzFramework::InputSystemCursorConstraintRequestBus::BroadcastResult(
systemCursorFocusWindow, &AzFramework::InputSystemCursorConstraintRequests::GetSystemCursorConstraintWindow);
if (!systemCursorFocusWindow)
if (systemCursorFocusWindow)
{
return XCB_NONE;
return static_cast<xcb_window_t>(reinterpret_cast<uint64_t>(systemCursorFocusWindow));
}
// TODO Clang compile error because cast .... loses information. On GNU/Linux HWND is void* and on 64-bit
// machines its obviously 64 bit but we receive the window id from m_renderOverlay.winId() which is xcb_window_t 32-bit.
// EWMH-compliant window managers set the "_NET_ACTIVE_WINDOW" property
// of the X server's root window to the currently active window. This
// retrieves value of that property.
return static_cast<xcb_window_t>(reinterpret_cast<uint64_t>(systemCursorFocusWindow));
// Get the atom for the _NET_ACTIVE_WINDOW property
constexpr int propertyNameLength = 18;
xcb_generic_error_t* error = nullptr;
XcbStdFreePtr<xcb_intern_atom_reply_t> activeWindowAtom {xcb_intern_atom_reply(
connection,
xcb_intern_atom(connection, /*only_if_exists=*/ 1, propertyNameLength, "_NET_ACTIVE_WINDOW"),
&error
)};
if (!activeWindowAtom || error)
{
if (error)
{
AZ_Warning("XcbInput", false, "Retrieving _NET_ACTIVE_WINDOW atom failed : Error code %d", error->error_code);
free(error);
}
return XCB_WINDOW_NONE;
}
// Get the root window
const xcb_window_t rootWId = xcb_setup_roots_iterator(xcb_get_setup(connection)).data->root;
// Fetch the value of the root window's _NET_ACTIVE_WINDOW property
XcbStdFreePtr<xcb_get_property_reply_t> property {xcb_get_property_reply(
connection,
xcb_get_property(
/*c=*/connection,
/*_delete=*/ 0,
/*window=*/rootWId,
/*property=*/activeWindowAtom->atom,
/*type=*/XCB_ATOM_WINDOW,
/*long_offset=*/0,
/*long_length=*/1
),
&error
)};
if (!property || error)
{
if (error)
{
AZ_Warning("XcbInput", false, "Retrieving _NET_ACTIVE_WINDOW atom failed : Error code %d", error->error_code);
free(error);
}
return XCB_WINDOW_NONE;
}
return *static_cast<xcb_window_t*>(xcb_get_property_value(property.get()));
}
xcb_connection_t* XcbInputDeviceMouse::s_xcbConnection = nullptr;
@ -39,8 +86,7 @@ namespace AzFramework
: InputDeviceMouse::Implementation(inputDevice)
, m_systemCursorState(SystemCursorState::Unknown)
, m_systemCursorPositionNormalized(0.5f, 0.5f)
, m_prevConstraintWindow(XCB_NONE)
, m_focusWindow(XCB_NONE)
, m_focusWindow(XCB_WINDOW_NONE)
, m_cursorShown(true)
{
XcbEventHandlerBus::Handler::BusConnect();
@ -57,14 +103,14 @@ namespace AzFramework
InputDeviceMouse::Implementation* XcbInputDeviceMouse::Create(InputDeviceMouse& inputDevice)
{
auto* interface = AzFramework::XcbConnectionManagerInterface::Get();
const auto* interface = AzFramework::XcbConnectionManagerInterface::Get();
if (!interface)
{
AZ_Warning("XcbInput", false, "XCB interface not available");
return nullptr;
}
s_xcbConnection = AzFramework::XcbConnectionManagerInterface::Get()->GetXcbConnection();
s_xcbConnection = interface->GetXcbConnection();
if (!s_xcbConnection)
{
AZ_Warning("XcbInput", false, "XCB connection not available");
@ -126,7 +172,7 @@ namespace AzFramework
// Get window information.
const XcbStdFreePtr<xcb_get_geometry_reply_t> xcbGeometryReply{ xcb_get_geometry_reply(
s_xcbConnection, xcb_get_geometry(s_xcbConnection, window), NULL) };
s_xcbConnection, xcb_get_geometry(s_xcbConnection, window), nullptr) };
if (!xcbGeometryReply)
{
@ -137,7 +183,7 @@ namespace AzFramework
xcb_translate_coordinates(s_xcbConnection, window, s_xcbScreen->root, 0, 0);
const XcbStdFreePtr<xcb_translate_coordinates_reply_t> xkbTranslateCoordReply{ xcb_translate_coordinates_reply(
s_xcbConnection, translate_coord, NULL) };
s_xcbConnection, translate_coord, nullptr) };
if (!xkbTranslateCoordReply)
{
@ -173,11 +219,11 @@ namespace AzFramework
for (const auto& barrier : m_activeBarriers)
{
xcb_void_cookie_t cookie = xcb_xfixes_create_pointer_barrier_checked(
s_xcbConnection, barrier.id, window, barrier.x0, barrier.y0, barrier.x1, barrier.y1, barrier.direction, 0, NULL);
const XcbStdFreePtr<xcb_generic_error_t> xkbError{ xcb_request_check(s_xcbConnection, cookie) };
s_xcbConnection, barrier.id, window, barrier.x0, barrier.y0, barrier.x1, barrier.y1, barrier.direction, 0, nullptr);
const XcbStdFreePtr<xcb_generic_error_t> xcbError{ xcb_request_check(s_xcbConnection, cookie) };
AZ_Warning(
"XcbInput", !xkbError, "XFixes, failed to create barrier %d at (%d %d %d %d)", barrier.id, barrier.x0, barrier.y0,
"XcbInput", !xcbError, "XFixes, failed to create barrier %d at (%d %d %d %d)", barrier.id, barrier.x0, barrier.y0,
barrier.x1, barrier.y1);
}
}
@ -207,7 +253,7 @@ namespace AzFramework
const xcb_xfixes_query_version_cookie_t query_cookie = xcb_xfixes_query_version(s_xcbConnection, 5, 0);
xcb_generic_error_t* error = NULL;
xcb_generic_error_t* error = nullptr;
const XcbStdFreePtr<xcb_xfixes_query_version_reply_t> xkbQueryRequestReply{ xcb_xfixes_query_version_reply(
s_xcbConnection, query_cookie, &error) };
@ -244,7 +290,7 @@ namespace AzFramework
const xcb_input_xi_query_version_cookie_t query_version_cookie = xcb_input_xi_query_version(s_xcbConnection, 2, 2);
xcb_generic_error_t* error = NULL;
xcb_generic_error_t* error = nullptr;
const XcbStdFreePtr<xcb_input_xi_query_version_reply_t> xkbQueryRequestReply{ xcb_input_xi_query_version_reply(
s_xcbConnection, query_version_cookie, &error) };
@ -268,40 +314,13 @@ namespace AzFramework
return m_xInputInitialized;
}
void XcbInputDeviceMouse::SetEnableXInput(bool enable)
{
struct
{
xcb_input_event_mask_t head;
int mask;
} mask;
mask.head.deviceid = XCB_INPUT_DEVICE_ALL;
mask.head.mask_len = 1;
if (enable)
{
mask.mask = XCB_INPUT_XI_EVENT_MASK_RAW_MOTION | XCB_INPUT_XI_EVENT_MASK_RAW_BUTTON_PRESS |
XCB_INPUT_XI_EVENT_MASK_RAW_BUTTON_RELEASE | XCB_INPUT_XI_EVENT_MASK_MOTION | XCB_INPUT_XI_EVENT_MASK_BUTTON_PRESS |
XCB_INPUT_XI_EVENT_MASK_BUTTON_RELEASE;
}
else
{
mask.mask = XCB_NONE;
}
xcb_input_xi_select_events(s_xcbConnection, s_xcbScreen->root, 1, &mask.head);
xcb_flush(s_xcbConnection);
}
void XcbInputDeviceMouse::SetSystemCursorState(SystemCursorState systemCursorState)
{
if (systemCursorState != m_systemCursorState)
{
m_systemCursorState = systemCursorState;
m_focusWindow = GetSystemCursorFocusWindow();
m_focusWindow = GetSystemCursorFocusWindow(s_xcbConnection);
HandleCursorState(m_focusWindow, systemCursorState);
}
@ -309,52 +328,10 @@ namespace AzFramework
void XcbInputDeviceMouse::HandleCursorState(xcb_window_t window, SystemCursorState systemCursorState)
{
bool confined = false, cursorShown = true;
switch (systemCursorState)
{
case SystemCursorState::ConstrainedAndHidden:
{
//!< Constrained to the application's main window and hidden
confined = true;
cursorShown = false;
}
break;
case SystemCursorState::ConstrainedAndVisible:
{
//!< Constrained to the application's main window and visible
confined = true;
}
break;
case SystemCursorState::UnconstrainedAndHidden:
{
//!< Free to move outside the main window but hidden while inside
cursorShown = false;
}
break;
case SystemCursorState::UnconstrainedAndVisible:
{
//!< Free to move outside the application's main window and visible
}
case SystemCursorState::Unknown:
default:
break;
}
// ATTN GetSystemCursorFocusWindow when getting out of the play in editor will return XCB_NONE
// We need however the window id to reset the cursor.
if (XCB_NONE == window && (confined || cursorShown))
{
// Reuse the previous window to reset states.
window = m_prevConstraintWindow;
m_prevConstraintWindow = XCB_NONE;
}
else
{
// Remember the window we used to modify cursor and barrier states.
m_prevConstraintWindow = window;
}
SetEnableXInput(!cursorShown);
const bool confined = (systemCursorState == SystemCursorState::ConstrainedAndHidden) ||
(systemCursorState == SystemCursorState::ConstrainedAndVisible);
const bool cursorShown = (systemCursorState == SystemCursorState::ConstrainedAndVisible) ||
(systemCursorState == SystemCursorState::UnconstrainedAndVisible);
CreateBarriers(window, confined);
ShowCursor(window, cursorShown);
@ -368,26 +345,26 @@ namespace AzFramework
void XcbInputDeviceMouse::SetSystemCursorPositionNormalizedInternal(xcb_window_t window, AZ::Vector2 positionNormalized)
{
// TODO Basically not done at all. Added only the basic functions needed.
const XcbStdFreePtr<xcb_get_geometry_reply_t> xkbGeometryReply{ xcb_get_geometry_reply(
s_xcbConnection, xcb_get_geometry(s_xcbConnection, window), NULL) };
const XcbStdFreePtr<xcb_get_geometry_reply_t> xcbGeometryReply{ xcb_get_geometry_reply(
s_xcbConnection, xcb_get_geometry(s_xcbConnection, window), nullptr) };
if (!xkbGeometryReply)
if (!xcbGeometryReply)
{
return;
}
const int16_t x = static_cast<int16_t>(positionNormalized.GetX() * xkbGeometryReply->width);
const int16_t y = static_cast<int16_t>(positionNormalized.GetY() * xkbGeometryReply->height);
const int16_t x = static_cast<int16_t>(positionNormalized.GetX() * xcbGeometryReply->width);
const int16_t y = static_cast<int16_t>(positionNormalized.GetY() * xcbGeometryReply->height);
xcb_warp_pointer(s_xcbConnection, XCB_NONE, window, 0, 0, 0, 0, x, y);
xcb_warp_pointer(s_xcbConnection, XCB_WINDOW_NONE, window, 0, 0, 0, 0, x, y);
xcb_flush(s_xcbConnection);
}
void XcbInputDeviceMouse::SetSystemCursorPositionNormalized(AZ::Vector2 positionNormalized)
{
const xcb_window_t window = GetSystemCursorFocusWindow();
if (XCB_NONE == window)
const xcb_window_t window = GetSystemCursorFocusWindow(s_xcbConnection);
if (XCB_WINDOW_NONE == window)
{
return;
}
@ -401,7 +378,7 @@ namespace AzFramework
const xcb_query_pointer_cookie_t pointer = xcb_query_pointer(s_xcbConnection, window);
const XcbStdFreePtr<xcb_query_pointer_reply_t> xkbQueryPointerReply{ xcb_query_pointer_reply(s_xcbConnection, pointer, NULL) };
const XcbStdFreePtr<xcb_query_pointer_reply_t> xkbQueryPointerReply{ xcb_query_pointer_reply(s_xcbConnection, pointer, nullptr) };
if (!xkbQueryPointerReply)
{
@ -409,7 +386,7 @@ namespace AzFramework
}
const XcbStdFreePtr<xcb_get_geometry_reply_t> xkbGeometryReply{ xcb_get_geometry_reply(
s_xcbConnection, xcb_get_geometry(s_xcbConnection, window), NULL) };
s_xcbConnection, xcb_get_geometry(s_xcbConnection, window), nullptr) };
if (!xkbGeometryReply)
{
@ -429,8 +406,8 @@ namespace AzFramework
AZ::Vector2 XcbInputDeviceMouse::GetSystemCursorPositionNormalized() const
{
const xcb_window_t window = GetSystemCursorFocusWindow();
if (XCB_NONE == window)
const xcb_window_t window = GetSystemCursorFocusWindow(s_xcbConnection);
if (XCB_WINDOW_NONE == window)
{
return AZ::Vector2::CreateZero();
}
@ -455,11 +432,11 @@ namespace AzFramework
cookie = xcb_xfixes_hide_cursor_checked(s_xcbConnection, window);
}
const XcbStdFreePtr<xcb_generic_error_t> xkbError{ xcb_request_check(s_xcbConnection, cookie) };
const XcbStdFreePtr<xcb_generic_error_t> xcbError{ xcb_request_check(s_xcbConnection, cookie) };
if (xkbError)
if (xcbError)
{
AZ_Warning("XcbInput", false, "ShowCursor failed: %d", xkbError->error_code);
AZ_Warning("XcbInput", false, "ShowCursor failed: %d", xcbError->error_code);
return;
}
@ -500,14 +477,6 @@ namespace AzFramework
}
}
void XcbInputDeviceMouse::HandlePointerMotionEvents(const xcb_generic_event_t* event)
{
const xcb_input_motion_event_t* mouseMotionEvent = reinterpret_cast<const xcb_input_motion_event_t*>(event);
m_systemCursorPosition[0] = mouseMotionEvent->event_x;
m_systemCursorPosition[1] = mouseMotionEvent->event_y;
}
void XcbInputDeviceMouse::HandleRawInputEvents(const xcb_ge_generic_event_t* event)
{
const xcb_ge_generic_event_t* genericEvent = reinterpret_cast<const xcb_ge_generic_event_t*>(event);
@ -552,78 +521,20 @@ namespace AzFramework
}
}
void XcbInputDeviceMouse::PollSpecialEvents()
{
while (xcb_generic_event_t* genericEvent = xcb_poll_for_queued_event(s_xcbConnection))
{
// TODO Is the following correct? If we are showing the cursor, don't poll RAW Input events.
switch (genericEvent->response_type & ~0x80)
{
case XCB_GE_GENERIC:
{
const xcb_ge_generic_event_t* geGenericEvent = reinterpret_cast<const xcb_ge_generic_event_t*>(genericEvent);
// Only handle raw inputs if we have focus.
// Handle Raw Input events first.
if ((geGenericEvent->event_type == XCB_INPUT_RAW_BUTTON_PRESS) ||
(geGenericEvent->event_type == XCB_INPUT_RAW_BUTTON_RELEASE) ||
(geGenericEvent->event_type == XCB_INPUT_RAW_MOTION))
{
HandleRawInputEvents(geGenericEvent);
free(genericEvent);
}
}
break;
}
}
}
void XcbInputDeviceMouse::HandleXcbEvent(xcb_generic_event_t* event)
{
switch (event->response_type & ~0x80)
{
// QT5 is using by default XInput which means we do need to check for XCB_GE_GENERIC event to parse all mouse related events.
// XInput raw events are sent from the server as a XCB_GE_GENERIC
// event. A XCB_GE_GENERIC event is typecast to a
// xcb_ge_generic_event_t, which is distinct from a
// xcb_generic_event_t, and exists so that X11 extensions can extend
// the event emission beyond the size that a normal X11 event could
// contain.
case XCB_GE_GENERIC:
{
const xcb_ge_generic_event_t* genericEvent = reinterpret_cast<const xcb_ge_generic_event_t*>(event);
// Handling RAW Inputs here works in GameMode but not in Editor mode because QT is
// not handling RAW input events and passing to.
if (!m_cursorShown)
{
// Handle Raw Input events first.
if ((genericEvent->event_type == XCB_INPUT_RAW_BUTTON_PRESS) ||
(genericEvent->event_type == XCB_INPUT_RAW_BUTTON_RELEASE) || (genericEvent->event_type == XCB_INPUT_RAW_MOTION))
{
HandleRawInputEvents(genericEvent);
}
}
else
{
switch (genericEvent->event_type)
{
case XCB_INPUT_BUTTON_PRESS:
{
const xcb_input_button_press_event_t* mouseButtonEvent =
reinterpret_cast<const xcb_input_button_press_event_t*>(genericEvent);
HandleButtonPressEvents(mouseButtonEvent->detail, true);
}
break;
case XCB_INPUT_BUTTON_RELEASE:
{
const xcb_input_button_release_event_t* mouseButtonEvent =
reinterpret_cast<const xcb_input_button_release_event_t*>(genericEvent);
HandleButtonPressEvents(mouseButtonEvent->detail, false);
}
break;
case XCB_INPUT_MOTION:
{
HandlePointerMotionEvents(event);
}
break;
}
}
HandleRawInputEvents(genericEvent);
}
break;
case XCB_FOCUS_IN:
@ -634,6 +545,9 @@ namespace AzFramework
m_focusWindow = focusInEvent->event;
HandleCursorState(m_focusWindow, m_systemCursorState);
}
auto* interface = AzFramework::XcbConnectionManagerInterface::Get();
interface->SetEnableXInput(interface->GetXcbConnection(), true);
}
break;
case XCB_FOCUS_OUT:
@ -644,7 +558,10 @@ namespace AzFramework
ProcessRawEventQueues();
ResetInputChannelStates();
m_focusWindow = XCB_NONE;
m_focusWindow = XCB_WINDOW_NONE;
auto* interface = AzFramework::XcbConnectionManagerInterface::Get();
interface->SetEnableXInput(interface->GetXcbConnection(), false);
}
break;
}

@ -65,9 +65,6 @@ namespace AzFramework
//! \ref AzFramework::InputDeviceMouse::Implementation::TickInputDevice
void TickInputDevice() override;
//! This method is called by the Editor to accommodate some events with the Editor. Never called in Game mode.
void PollSpecialEvents() override;
//! Handle X11 events.
void HandleXcbEvent(xcb_generic_event_t* event) override;
@ -77,9 +74,6 @@ namespace AzFramework
//! Initialize XInput extension. Used for raw input during confinement and showing/hiding the cursor.
static bool InitializeXInput();
//! Enables/Disables XInput Raw Input events.
void SetEnableXInput(bool enable);
//! Create barriers.
void CreateBarriers(xcb_window_t window, bool create);
@ -98,9 +92,6 @@ namespace AzFramework
//! Handle button press/release events.
void HandleButtonPressEvents(uint32_t detail, bool pressed);
//! Handle motion notify events.
void HandlePointerMotionEvents(const xcb_generic_event_t* event);
//! Will set cursor states and confinement modes.
void HandleCursorState(xcb_window_t window, SystemCursorState systemCursorState);
@ -160,7 +151,6 @@ namespace AzFramework
AZ::Vector2 m_cursorHiddenPosition;
AZ::Vector2 m_systemCursorPositionNormalized;
uint32_t m_systemCursorPosition[MAX_XI_RAW_AXIS];
static xcb_connection_t* s_xcbConnection;
static xcb_screen_t* s_xcbScreen;
@ -171,9 +161,6 @@ namespace AzFramework
//! Will be true if the xinput2 extension could be initialized.
static bool m_xInputInitialized;
//! The window that had focus
xcb_window_t m_prevConstraintWindow;
//! The current window that has focus
xcb_window_t m_focusWindow;

@ -11,6 +11,13 @@
#include <gtest/gtest.h>
#include <gmock/gmock.h>
ACTION_TEMPLATE(ReturnMalloc,
HAS_1_TEMPLATE_PARAMS(typename, T),
AND_0_VALUE_PARAMS()) {
T* value = static_cast<T*>(malloc(sizeof(T)));
*value = T{};
return value;
}
ACTION_TEMPLATE(ReturnMalloc,
HAS_1_TEMPLATE_PARAMS(typename, T),
AND_1_VALUE_PARAMS(p0)) {
@ -25,3 +32,38 @@ ACTION_TEMPLATE(ReturnMalloc,
*value = T{ p0, p1 };
return value;
}
ACTION_TEMPLATE(ReturnMalloc,
HAS_1_TEMPLATE_PARAMS(typename, T),
AND_3_VALUE_PARAMS(p0, p1, p2)) {
T* value = static_cast<T*>(malloc(sizeof(T)));
*value = T{ p0, p1, p2 };
return value;
}
ACTION_TEMPLATE(ReturnMalloc,
HAS_1_TEMPLATE_PARAMS(typename, T),
AND_4_VALUE_PARAMS(p0, p1, p2, p3)) {
T* value = static_cast<T*>(malloc(sizeof(T)));
*value = T{ p0, p1, p2, p3 };
return value;
}
ACTION_TEMPLATE(ReturnMalloc,
HAS_1_TEMPLATE_PARAMS(typename, T),
AND_5_VALUE_PARAMS(p0, p1, p2, p3, p4)) {
T* value = static_cast<T*>(malloc(sizeof(T)));
*value = T{ p0, p1, p2, p3, p4 };
return value;
}
ACTION_TEMPLATE(ReturnMalloc,
HAS_1_TEMPLATE_PARAMS(typename, T),
AND_6_VALUE_PARAMS(p0, p1, p2, p3, p4, p5)) {
T* value = static_cast<T*>(malloc(sizeof(T)));
*value = T{ p0, p1, p2, p3, p4, p5 };
return value;
}
ACTION_TEMPLATE(ReturnMalloc,
HAS_1_TEMPLATE_PARAMS(typename, T),
AND_7_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6)) {
T* value = static_cast<T*>(malloc(sizeof(T)));
*value = T{ p0, p1, p2, p3, p4, p5, p6 };
return value;
}

@ -32,6 +32,82 @@ xcb_generic_error_t* xcb_request_check(xcb_connection_t* c, xcb_void_cookie_t co
{
return MockXcbInterface::Instance()->xcb_request_check(c, cookie);
}
const xcb_setup_t* xcb_get_setup(xcb_connection_t *c)
{
return MockXcbInterface::Instance()->xcb_get_setup(c);
}
xcb_screen_iterator_t xcb_setup_roots_iterator(const xcb_setup_t* R)
{
return MockXcbInterface::Instance()->xcb_setup_roots_iterator(R);
}
const xcb_query_extension_reply_t* xcb_get_extension_data(xcb_connection_t* c, xcb_extension_t* ext)
{
return MockXcbInterface::Instance()->xcb_get_extension_data(c, ext);
}
int xcb_flush(xcb_connection_t *c)
{
return MockXcbInterface::Instance()->xcb_flush(c);
}
xcb_query_pointer_cookie_t xcb_query_pointer(xcb_connection_t* c, xcb_window_t window)
{
return MockXcbInterface::Instance()->xcb_query_pointer(c, window);
}
xcb_query_pointer_reply_t* xcb_query_pointer_reply(xcb_connection_t* c, xcb_query_pointer_cookie_t cookie, xcb_generic_error_t** e)
{
return MockXcbInterface::Instance()->xcb_query_pointer_reply(c, cookie, e);
}
xcb_get_geometry_cookie_t xcb_get_geometry(xcb_connection_t* c, xcb_drawable_t drawable)
{
return MockXcbInterface::Instance()->xcb_get_geometry(c, drawable);
}
xcb_get_geometry_reply_t* xcb_get_geometry_reply(xcb_connection_t* c, xcb_get_geometry_cookie_t cookie, xcb_generic_error_t** e)
{
return MockXcbInterface::Instance()->xcb_get_geometry_reply(c, cookie, e);
}
xcb_void_cookie_t xcb_warp_pointer(
xcb_connection_t* c,
xcb_window_t src_window,
xcb_window_t dst_window,
int16_t src_x,
int16_t src_y,
uint16_t src_width,
uint16_t src_height,
int16_t dst_x,
int16_t dst_y)
{
return MockXcbInterface::Instance()->xcb_warp_pointer(c, src_window, dst_window, src_x, src_y, src_width, src_height, dst_x, dst_y);
}
xcb_intern_atom_cookie_t xcb_intern_atom(xcb_connection_t* c, uint8_t only_if_exists, uint16_t name_len, const char* name)
{
return MockXcbInterface::Instance()->xcb_intern_atom(c, only_if_exists, name_len, name);
}
xcb_intern_atom_reply_t* xcb_intern_atom_reply(xcb_connection_t* c, xcb_intern_atom_cookie_t cookie, xcb_generic_error_t** e)
{
return MockXcbInterface::Instance()->xcb_intern_atom_reply(c, cookie, e);
}
xcb_get_property_cookie_t xcb_get_property(
xcb_connection_t* c,
uint8_t _delete,
xcb_window_t window,
xcb_atom_t property,
xcb_atom_t type,
uint32_t long_offset,
uint32_t long_length)
{
return MockXcbInterface::Instance()->xcb_get_property(c, _delete, window, property, type, long_offset, long_length);
}
xcb_get_property_reply_t* xcb_get_property_reply(xcb_connection_t* c, xcb_get_property_cookie_t cookie, xcb_generic_error_t** e)
{
return MockXcbInterface::Instance()->xcb_get_property_reply(c, cookie, e);
}
void* xcb_get_property_value(const xcb_get_property_reply_t* R)
{
return MockXcbInterface::Instance()->xcb_get_property_value(R);
}
uint32_t xcb_generate_id(xcb_connection_t *c)
{
return MockXcbInterface::Instance()->xcb_generate_id(c);
}
// ----------------------------------------------------------------------------
// xcb-xkb
@ -116,4 +192,76 @@ xkb_state_component xkb_state_update_mask(
state, depressed_mods, latched_mods, locked_mods, depressed_layout, latched_layout, locked_layout);
}
// ----------------------------------------------------------------------------
// xcb-xfixes
xcb_xfixes_query_version_cookie_t xcb_xfixes_query_version(
xcb_connection_t* c, uint32_t client_major_version, uint32_t client_minor_version)
{
return MockXcbInterface::Instance()->xcb_xfixes_query_version(c, client_major_version, client_minor_version);
}
xcb_xfixes_query_version_reply_t* xcb_xfixes_query_version_reply(
xcb_connection_t* c, xcb_xfixes_query_version_cookie_t cookie, xcb_generic_error_t** e)
{
return MockXcbInterface::Instance()->xcb_xfixes_query_version_reply(c, cookie, e);
}
xcb_void_cookie_t xcb_xfixes_show_cursor_checked(xcb_connection_t* c, xcb_window_t window)
{
return MockXcbInterface::Instance()->xcb_xfixes_show_cursor_checked(c, window);
}
xcb_void_cookie_t xcb_xfixes_hide_cursor_checked(xcb_connection_t* c, xcb_window_t window)
{
return MockXcbInterface::Instance()->xcb_xfixes_hide_cursor_checked(c, window);
}
xcb_void_cookie_t xcb_xfixes_delete_pointer_barrier_checked(xcb_connection_t* c, xcb_xfixes_barrier_t barrier)
{
return MockXcbInterface::Instance()->xcb_xfixes_delete_pointer_barrier_checked(c, barrier);
}
xcb_translate_coordinates_cookie_t xcb_translate_coordinates(xcb_connection_t* c, xcb_window_t src_window, xcb_window_t dst_window, int16_t src_x, int16_t src_y)
{
return MockXcbInterface::Instance()->xcb_translate_coordinates(c, src_window, dst_window, src_x, src_y);
}
xcb_translate_coordinates_reply_t* xcb_translate_coordinates_reply(xcb_connection_t* c, xcb_translate_coordinates_cookie_t cookie, xcb_generic_error_t** e)
{
return MockXcbInterface::Instance()->xcb_translate_coordinates_reply(c, cookie, e);
}
xcb_void_cookie_t xcb_xfixes_create_pointer_barrier_checked(
xcb_connection_t* c,
xcb_xfixes_barrier_t barrier,
xcb_window_t window,
uint16_t x1,
uint16_t y1,
uint16_t x2,
uint16_t y2,
uint32_t directions,
uint16_t num_devices,
const uint16_t* devices)
{
return MockXcbInterface::Instance()->xcb_xfixes_create_pointer_barrier_checked(c, barrier, window, x1, y1, x2, y2, directions, num_devices, devices);
}
// ----------------------------------------------------------------------------
// xcb-xinput
xcb_input_xi_query_version_cookie_t xcb_input_xi_query_version(xcb_connection_t* c, uint16_t major_version, uint16_t minor_version)
{
return MockXcbInterface::Instance()->xcb_input_xi_query_version(c, major_version, minor_version);
}
xcb_input_xi_query_version_reply_t* xcb_input_xi_query_version_reply(
xcb_connection_t* c, xcb_input_xi_query_version_cookie_t cookie, xcb_generic_error_t** e)
{
return MockXcbInterface::Instance()->xcb_input_xi_query_version_reply(c, cookie, e);
}
xcb_void_cookie_t xcb_input_xi_select_events(
xcb_connection_t* c, xcb_window_t window, uint16_t num_mask, const xcb_input_event_mask_t* masks)
{
return MockXcbInterface::Instance()->xcb_input_xi_select_events(c, window, num_mask, masks);
}
int xcb_input_raw_button_press_axisvalues_length (const xcb_input_raw_button_press_event_t *R)
{
return MockXcbInterface::Instance()->xcb_input_raw_button_press_axisvalues_length(R);
}
xcb_input_fp3232_t* xcb_input_raw_button_press_axisvalues_raw(const xcb_input_raw_button_press_event_t* R)
{
return MockXcbInterface::Instance()->xcb_input_raw_button_press_axisvalues_raw(R);
}
}

@ -18,6 +18,8 @@
#undef explicit
#include <xkbcommon/xkbcommon.h>
#include <xkbcommon/xkbcommon-x11.h>
#include <xcb/xfixes.h>
#include <xcb/xinput.h>
#include "Printers.h"
@ -62,6 +64,37 @@ public:
MOCK_CONST_METHOD1(xcb_disconnect, void(xcb_connection_t* c));
MOCK_CONST_METHOD1(xcb_poll_for_event, xcb_generic_event_t*(xcb_connection_t* c));
MOCK_CONST_METHOD2(xcb_request_check, xcb_generic_error_t*(xcb_connection_t* c, xcb_void_cookie_t cookie));
MOCK_CONST_METHOD1(xcb_get_setup, const xcb_setup_t*(xcb_connection_t *c));
MOCK_CONST_METHOD1(xcb_setup_roots_iterator, xcb_screen_iterator_t(const xcb_setup_t* R));
MOCK_CONST_METHOD2(xcb_get_extension_data, const xcb_query_extension_reply_t*(xcb_connection_t* c, xcb_extension_t* ext));
MOCK_CONST_METHOD1(xcb_flush, int(xcb_connection_t *c));
MOCK_CONST_METHOD2(xcb_query_pointer, xcb_query_pointer_cookie_t(xcb_connection_t* c, xcb_window_t window));
MOCK_CONST_METHOD3(xcb_query_pointer_reply, xcb_query_pointer_reply_t*(xcb_connection_t* c, xcb_query_pointer_cookie_t cookie, xcb_generic_error_t** e));
MOCK_CONST_METHOD2(xcb_get_geometry, xcb_get_geometry_cookie_t(xcb_connection_t* c, xcb_drawable_t drawable));
MOCK_CONST_METHOD3(xcb_get_geometry_reply, xcb_get_geometry_reply_t*(xcb_connection_t* c, xcb_get_geometry_cookie_t cookie, xcb_generic_error_t** e));
MOCK_CONST_METHOD9(xcb_warp_pointer, xcb_void_cookie_t(
xcb_connection_t* c,
xcb_window_t src_window,
xcb_window_t dst_window,
int16_t src_x,
int16_t src_y,
uint16_t src_width,
uint16_t src_height,
int16_t dst_x,
int16_t dst_y));
MOCK_CONST_METHOD4(xcb_intern_atom, xcb_intern_atom_cookie_t(xcb_connection_t* c, uint8_t only_if_exists, uint16_t name_len, const char* name));
MOCK_CONST_METHOD3(xcb_intern_atom_reply, xcb_intern_atom_reply_t*(xcb_connection_t* c, xcb_intern_atom_cookie_t cookie, xcb_generic_error_t** e));
MOCK_CONST_METHOD7(xcb_get_property, xcb_get_property_cookie_t(
xcb_connection_t* c,
uint8_t _delete,
xcb_window_t window,
xcb_atom_t property,
xcb_atom_t type,
uint32_t long_offset,
uint32_t long_length));
MOCK_CONST_METHOD3(xcb_get_property_reply, xcb_get_property_reply_t*(xcb_connection_t* c, xcb_get_property_cookie_t cookie, xcb_generic_error_t** e));
MOCK_CONST_METHOD1(xcb_get_property_value, void*(const xcb_get_property_reply_t* R));
MOCK_CONST_METHOD1(xcb_generate_id, uint32_t(xcb_connection_t *c));
// xcb-xkb
MOCK_CONST_METHOD3(xcb_xkb_use_extension, xcb_xkb_use_extension_cookie_t(xcb_connection_t* c, uint16_t wantedMajor, uint16_t wantedMinor));
@ -83,6 +116,33 @@ public:
MOCK_CONST_METHOD4(xkb_state_key_get_utf8, int(xkb_state* state, xkb_keycode_t key, char* buffer, size_t size));
MOCK_CONST_METHOD7(xkb_state_update_mask, xkb_state_component(xkb_state* state, xkb_mod_mask_t depressed_mods, xkb_mod_mask_t latched_mods, xkb_mod_mask_t locked_mods, xkb_layout_index_t depressed_layout, xkb_layout_index_t latched_layout, xkb_layout_index_t locked_layout));
// xcb-xfixes
MOCK_CONST_METHOD3(xcb_xfixes_query_version, xcb_xfixes_query_version_cookie_t(xcb_connection_t* c, uint32_t client_major_version, uint32_t client_minor_version));
MOCK_CONST_METHOD3(xcb_xfixes_query_version_reply, xcb_xfixes_query_version_reply_t*(xcb_connection_t* c, xcb_xfixes_query_version_cookie_t cookie, xcb_generic_error_t** e));
MOCK_CONST_METHOD2(xcb_xfixes_show_cursor_checked, xcb_void_cookie_t(xcb_connection_t* c, xcb_window_t window));
MOCK_CONST_METHOD2(xcb_xfixes_hide_cursor_checked, xcb_void_cookie_t(xcb_connection_t* c, xcb_window_t window));
MOCK_CONST_METHOD2(xcb_xfixes_delete_pointer_barrier_checked, xcb_void_cookie_t(xcb_connection_t* c, xcb_xfixes_barrier_t barrier));
MOCK_CONST_METHOD5(xcb_translate_coordinates, xcb_translate_coordinates_cookie_t(xcb_connection_t* c, xcb_window_t src_window, xcb_window_t dst_window, int16_t src_x, int16_t src_y));
MOCK_CONST_METHOD3(xcb_translate_coordinates_reply, xcb_translate_coordinates_reply_t*(xcb_connection_t* c, xcb_translate_coordinates_cookie_t cookie, xcb_generic_error_t** e));
MOCK_CONST_METHOD10(xcb_xfixes_create_pointer_barrier_checked, xcb_void_cookie_t(
xcb_connection_t* c,
xcb_xfixes_barrier_t barrier,
xcb_window_t window,
uint16_t x1,
uint16_t y1,
uint16_t x2,
uint16_t y2,
uint32_t directions,
uint16_t num_devices,
const uint16_t* devices));
// xcb-xinput
MOCK_CONST_METHOD3(xcb_input_xi_query_version, xcb_input_xi_query_version_cookie_t(xcb_connection_t* c, uint16_t major_version, uint16_t minor_version));
MOCK_CONST_METHOD3(xcb_input_xi_query_version_reply, xcb_input_xi_query_version_reply_t*(xcb_connection_t* c, xcb_input_xi_query_version_cookie_t cookie, xcb_generic_error_t** e));
MOCK_CONST_METHOD4(xcb_input_xi_select_events, xcb_void_cookie_t(xcb_connection_t* c, xcb_window_t window, uint16_t num_mask, const xcb_input_event_mask_t* masks));
MOCK_CONST_METHOD1(xcb_input_raw_button_press_axisvalues_length, int(const xcb_input_raw_button_press_event_t* R));
MOCK_CONST_METHOD1(xcb_input_raw_button_press_axisvalues_raw, xcb_input_fp3232_t*(const xcb_input_raw_button_press_event_t* R));
private:
static inline MockXcbInterface* self = nullptr;
};

@ -22,6 +22,12 @@ namespace AzFramework
public:
void SetUp() override;
template<typename T>
static xcb_generic_event_t MakeEvent(T event)
{
return *reinterpret_cast<xcb_generic_event_t*>(&event);
}
protected:
testing::NiceMock<MockXcbInterface> m_interface;
xcb_connection_t m_connection{};

@ -21,12 +21,6 @@
#include "XcbBaseTestFixture.h"
#include "XcbTestApplication.h"
template<typename T>
xcb_generic_event_t MakeEvent(T event)
{
return *reinterpret_cast<xcb_generic_event_t*>(&event);
}
namespace AzFramework
{
// Sets up default behavior for mock keyboard responses to xcb methods

@ -0,0 +1,545 @@
/*
* 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::Eq;
using testing::Field;
using testing::Return;
using testing::StrEq;
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
));
// Set the default focus window
EXPECT_CALL(m_interface, xcb_intern_atom(&m_connection, 1, 18, StrEq("_NET_ACTIVE_WINDOW")))
.WillRepeatedly(Return(xcb_intern_atom_cookie_t{/*.sequence=*/ 1}));
ON_CALL(m_interface, xcb_intern_atom_reply(&m_connection, Field(&xcb_intern_atom_cookie_t::sequence, Eq(1)), _))
.WillByDefault(ReturnMalloc<xcb_intern_atom_reply_t>(
/*response_type=*/(uint8_t)XCB_INTERN_ATOM,
/*pad0=*/(uint8_t)0,
/*sequence=*/(uint16_t)1,
/*length=*/0u,
/*xcb_atom_t=*/s_netActiveWindowAtom
));
ON_CALL(m_interface, xcb_get_property(&m_connection, 0, s_rootWindow, s_netActiveWindowAtom, XCB_ATOM_WINDOW, 0, 1))
.WillByDefault(Return(xcb_get_property_cookie_t{/*.sequence=*/ s_getActiveWindowPropertySequence}));
ON_CALL(m_interface, xcb_get_property_reply(&m_connection, Field(&xcb_get_property_cookie_t::sequence, Eq(s_getActiveWindowPropertySequence)), _))
.WillByDefault(ReturnMalloc<xcb_get_property_reply_t>(
/*response_type=*/(uint8_t)XCB_GET_PROPERTY,
/*format=*/(uint8_t)0,
/*sequence=*/(uint16_t)s_getActiveWindowPropertySequence,
/*length=*/0u,
/*type=*/XCB_ATOM_WINDOW,
/*bytes_after=*/0u,
/*value_len=*/1u
));
ON_CALL(m_interface, xcb_get_property_value(Field(&xcb_get_property_reply_t::sequence, Eq(s_getActiveWindowPropertySequence))))
.WillByDefault(Return(const_cast<xcb_window_t*>(&s_nullWindow)));
ON_CALL(m_interface, xcb_get_geometry(&m_connection, _))
.WillByDefault(Return(xcb_get_geometry_cookie_t{/*.sequence=*/1}));
ON_CALL(m_interface, xcb_get_geometry_reply(&m_connection, Field(&xcb_get_geometry_cookie_t::sequence, Eq(1)), _))
.WillByDefault(ReturnMalloc<xcb_get_geometry_reply_t>(s_defaultWindowGeometry));
}
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_window_t s_nullWindow = XCB_WINDOW_NONE;
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 uint16_t s_getActiveWindowPropertySequence = 2160;
static constexpr inline xcb_atom_t s_netActiveWindowAtom = 1;
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,
};
static constexpr inline xcb_get_geometry_reply_t s_defaultWindowGeometry{
/*.response_type=*/XCB_GET_GEOMETRY,
/*.depth=*/0,
/*.sequence=*/1,
/*.length=*/0,
/*.root=*/s_rootWindow,
/*.x=*/100,
/*.y=*/100,
/*.width=*/100,
/*.height=*/100,
/*.border_width=*/3,
/*.pad0[2]=*/{},
};
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));
}
struct GetCursorPositionParam
{
int16_t m_x;
int16_t m_y;
};
class XcbGetSystemCursorPositionTests
: public XcbInputDeviceMouseTests
, public testing::WithParamInterface<GetCursorPositionParam>
{
};
TEST_P(XcbGetSystemCursorPositionTests, GetSystemCursorPositionNormalizedReturnsCorrectValue)
{
using testing::Eq;
using testing::Field;
using testing::Return;
using testing::_;
xcb_window_t focusWindow = 42;
const xcb_query_pointer_reply_t queryPointerReply{
/*.response_type=*/XCB_QUERY_POINTER,
/*.same_screen=*/1,
/*.sequence=*/0,
/*.length=*/1,
/*.root=*/s_rootWindow,
/*.child=*/focusWindow,
/*.root_x=*/static_cast<int16_t>(GetParam().m_x + s_defaultWindowGeometry.x),
/*.root_y=*/static_cast<int16_t>(GetParam().m_y + s_defaultWindowGeometry.y),
/*.win_x=*/GetParam().m_x,
/*.win_y=*/GetParam().m_y,
/*.mask=*/{},
/*.pad0[2]=*/{},
};
// Querying the root window's pointer gives its absolute value
const xcb_query_pointer_reply_t rootWindowQueryPointerReply{
/*.response_type=*/XCB_QUERY_POINTER,
/*.same_screen=*/1,
/*.sequence=*/0,
/*.length=*/1,
/*.root=*/s_rootWindow,
/*.child=*/s_rootWindow,
/*.root_x=*/static_cast<int16_t>(GetParam().m_x + s_defaultWindowGeometry.x),
/*.root_y=*/static_cast<int16_t>(GetParam().m_y + s_defaultWindowGeometry.y),
/*.win_x=*/static_cast<int16_t>(GetParam().m_x + s_defaultWindowGeometry.x),
/*.win_y=*/static_cast<int16_t>(GetParam().m_y + s_defaultWindowGeometry.y),
/*.mask=*/{},
/*.pad0[2]=*/{},
};
EXPECT_CALL(m_interface, xcb_get_property_value(Field(&xcb_get_property_reply_t::sequence, Eq(s_getActiveWindowPropertySequence))))
.WillRepeatedly(Return(&focusWindow));
EXPECT_CALL(m_interface, xcb_query_pointer(&m_connection, focusWindow))
.WillRepeatedly(Return(xcb_query_pointer_cookie_t{/*.sequence=*/1}));
EXPECT_CALL(m_interface, xcb_query_pointer_reply(&m_connection, Field(&xcb_query_pointer_cookie_t::sequence, 1), _))
.WillRepeatedly(ReturnMalloc<xcb_query_pointer_reply_t>(queryPointerReply));
EXPECT_CALL(m_interface, xcb_query_pointer(&m_connection, s_rootWindow))
.WillRepeatedly(Return(xcb_query_pointer_cookie_t{/*.sequence=*/2}));
EXPECT_CALL(m_interface, xcb_query_pointer_reply(&m_connection, Field(&xcb_query_pointer_cookie_t::sequence, 2), _))
.WillRepeatedly(ReturnMalloc<xcb_query_pointer_reply_t>(rootWindowQueryPointerReply));
m_application.Start();
InputSystemCursorRequestBus::Event(
InputDeviceMouse::Id,
&InputSystemCursorRequests::SetSystemCursorState,
SystemCursorState::ConstrainedAndHidden);
AZ::Vector2 systemCursorPositionNormalized = AZ::Vector2::CreateZero();
InputSystemCursorRequestBus::EventResult(
systemCursorPositionNormalized,
InputDeviceMouse::Id,
&InputSystemCursorRequests::GetSystemCursorPositionNormalized);
EXPECT_THAT(systemCursorPositionNormalized, ::testing::AllOf(
testing::Property(&AZ::Vector2::GetX, testing::FloatEq(static_cast<float>(GetParam().m_x) / s_defaultWindowGeometry.width)),
testing::Property(&AZ::Vector2::GetY, testing::FloatEq(static_cast<float>(GetParam().m_y) / s_defaultWindowGeometry.height))
));
}
INSTANTIATE_TEST_CASE_P(
AllPointerPositions,
XcbGetSystemCursorPositionTests,
testing::Values(
// Default mocked window geometry sets width and height to 100, all
// parameter values should be within [0, 100)
GetCursorPositionParam{ 50, 50 },
GetCursorPositionParam{ 25, 25 },
GetCursorPositionParam{ 0, 100 }
)
);
} // namespace AzFramework

@ -17,5 +17,6 @@ set(FILES
XcbBaseTestFixture.cpp
XcbBaseTestFixture.h
XcbInputDeviceKeyboardTests.cpp
XcbInputDeviceMouseTests.cpp
XcbTestApplication.h
)

Loading…
Cancel
Save