Merge pull request #4595 from yaakuro/yaakuro-development-patch-1

[GNU/Linux] Add basic mouse device implementation and fullscreen handling to GNU/…
This commit is contained in:
Pratik Patel
2021-10-15 15:28:15 -07:00
committed by GitHub
10 changed files with 1177 additions and 98 deletions
@@ -19,10 +19,16 @@ namespace Editor
if (GetIEditor()->IsInGameMode())
{
#ifdef PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB
AzFramework::XcbEventHandlerBus::Broadcast(&AzFramework::XcbEventHandler::HandleXcbEvent, static_cast<xcb_generic_event_t*>(message));
// 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
return true;
}
return false;
}
}
} // namespace Editor
@@ -23,10 +23,12 @@ 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
class XcbEventHandlerBusTraits : public AZ::EBusTraits
{
public:
//////////////////////////////////////////////////////////////////////////
@@ -0,0 +1,652 @@
/*
* 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 <AzCore/std/typetraits/integral_constant.h>
#include <AzFramework/API/ApplicationAPI_Linux.h>
#include <AzFramework/XcbConnectionManager.h>
#include <AzFramework/XcbInputDeviceMouse.h>
namespace AzFramework
{
xcb_window_t GetSystemCursorFocusWindow()
{
void* systemCursorFocusWindow = nullptr;
AzFramework::InputSystemCursorConstraintRequestBus::BroadcastResult(
systemCursorFocusWindow, &AzFramework::InputSystemCursorConstraintRequests::GetSystemCursorConstraintWindow);
if (!systemCursorFocusWindow)
{
return XCB_NONE;
}
// 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.
return static_cast<xcb_window_t>(reinterpret_cast<uint64_t>(systemCursorFocusWindow));
}
xcb_connection_t* XcbInputDeviceMouse::s_xcbConnection = nullptr;
xcb_screen_t* XcbInputDeviceMouse::s_xcbScreen = nullptr;
bool XcbInputDeviceMouse::m_xfixesInitialized = false;
bool XcbInputDeviceMouse::m_xInputInitialized = false;
XcbInputDeviceMouse::XcbInputDeviceMouse(InputDeviceMouse& inputDevice)
: InputDeviceMouse::Implementation(inputDevice)
, m_systemCursorState(SystemCursorState::Unknown)
, m_systemCursorPositionNormalized(0.5f, 0.5f)
, m_prevConstraintWindow(XCB_NONE)
, m_focusWindow(XCB_NONE)
, m_cursorShown(true)
{
XcbEventHandlerBus::Handler::BusConnect();
SetSystemCursorState(SystemCursorState::Unknown);
}
XcbInputDeviceMouse::~XcbInputDeviceMouse()
{
XcbEventHandlerBus::Handler::BusDisconnect();
SetSystemCursorState(SystemCursorState::Unknown);
}
InputDeviceMouse::Implementation* XcbInputDeviceMouse::Create(InputDeviceMouse& inputDevice)
{
auto* interface = AzFramework::XcbConnectionManagerInterface::Get();
if (!interface)
{
AZ_Warning("XcbInput", false, "XCB interface not available");
return nullptr;
}
s_xcbConnection = AzFramework::XcbConnectionManagerInterface::Get()->GetXcbConnection();
if (!s_xcbConnection)
{
AZ_Warning("XcbInput", false, "XCB connection not available");
return nullptr;
}
const xcb_setup_t* xcbSetup = xcb_get_setup(s_xcbConnection);
s_xcbScreen = xcb_setup_roots_iterator(xcbSetup).data;
if (!s_xcbScreen)
{
AZ_Warning("XcbInput", false, "XCB screen not available");
return nullptr;
}
// Initialize XFixes extension which we use to create pointer barriers.
if (!InitializeXFixes())
{
AZ_Warning("XcbInput", false, "XCB XFixes initialization failed");
return nullptr;
}
// Initialize XInput extension which is used to get RAW Input events.
if (!InitializeXInput())
{
AZ_Warning("XcbInput", false, "XCB XInput initialization failed");
return nullptr;
}
return aznew XcbInputDeviceMouse(inputDevice);
}
bool XcbInputDeviceMouse::IsConnected() const
{
return true;
}
void XcbInputDeviceMouse::CreateBarriers(xcb_window_t window, bool create)
{
// Don't create any barriers if we are debugging. This will cause artifacts but better then
// a confined cursor during debugging.
if (AZ::Debug::Trace::IsDebuggerPresent())
{
AZ_Warning("XcbInput", false, "Debugger running. Barriers will not be created.");
return;
}
if (create)
{
// Destroy barriers if they are active already.
if (!m_activeBarriers.empty())
{
for (const auto& barrier : m_activeBarriers)
{
xcb_xfixes_delete_pointer_barrier_checked(s_xcbConnection, barrier.id);
}
m_activeBarriers.clear();
}
// Get window information.
const XcbStdFreePtr<xcb_get_geometry_reply_t> xcbGeometryReply{ xcb_get_geometry_reply(
s_xcbConnection, xcb_get_geometry(s_xcbConnection, window), NULL) };
if (!xcbGeometryReply)
{
return;
}
const xcb_translate_coordinates_cookie_t translate_coord =
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) };
if (!xkbTranslateCoordReply)
{
return;
}
const int16_t x0 = xkbTranslateCoordReply->dst_x < 0 ? 0 : xkbTranslateCoordReply->dst_x;
const int16_t y0 = xkbTranslateCoordReply->dst_y < 0 ? 0 : xkbTranslateCoordReply->dst_y;
const int16_t x1 = xkbTranslateCoordReply->dst_x + xcbGeometryReply->width;
const int16_t y1 = xkbTranslateCoordReply->dst_y + xcbGeometryReply->height;
// ATTN For whatever reason, when making an exact rectangle the pointer will escape the top right corner in some cases. Adding
// an offset to the lines so that they cross each other prevents that.
const int16_t offset = 30;
// Create the left barrier info.
m_activeBarriers.push_back({ xcb_generate_id(s_xcbConnection), XCB_XFIXES_BARRIER_DIRECTIONS_POSITIVE_X, x0, Clamp(y0 - offset),
x0, Clamp(y1 + offset) });
// Create the right barrier info.
m_activeBarriers.push_back({ xcb_generate_id(s_xcbConnection), XCB_XFIXES_BARRIER_DIRECTIONS_NEGATIVE_X, x1, Clamp(y0 - offset),
x1, Clamp(y1 + offset) });
// Create the top barrier info.
m_activeBarriers.push_back({ xcb_generate_id(s_xcbConnection), XCB_XFIXES_BARRIER_DIRECTIONS_POSITIVE_Y, Clamp(x0 - offset), y0,
Clamp(x1 + offset), y0 });
// Create the bottom barrier info.
m_activeBarriers.push_back({ xcb_generate_id(s_xcbConnection), XCB_XFIXES_BARRIER_DIRECTIONS_NEGATIVE_Y, Clamp(x0 - offset), y1,
Clamp(x1 + offset), y1 });
// Create the xfixes barriers.
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) };
AZ_Warning(
"XcbInput", !xkbError, "XFixes, failed to create barrier %d at (%d %d %d %d)", barrier.id, barrier.x0, barrier.y0,
barrier.x1, barrier.y1);
}
}
else
{
for (const auto& barrier : m_activeBarriers)
{
xcb_xfixes_delete_pointer_barrier_checked(s_xcbConnection, barrier.id);
}
m_activeBarriers.clear();
}
xcb_flush(s_xcbConnection);
}
bool XcbInputDeviceMouse::InitializeXFixes()
{
m_xfixesInitialized = false;
// We don't have to free query_extension_reply according to xcb documentation.
const xcb_query_extension_reply_t* query_extension_reply = xcb_get_extension_data(s_xcbConnection, &xcb_xfixes_id);
if (!query_extension_reply || !query_extension_reply->present)
{
return m_xfixesInitialized;
}
const xcb_xfixes_query_version_cookie_t query_cookie = xcb_xfixes_query_version(s_xcbConnection, 5, 0);
xcb_generic_error_t* error = NULL;
const XcbStdFreePtr<xcb_xfixes_query_version_reply_t> xkbQueryRequestReply{ xcb_xfixes_query_version_reply(
s_xcbConnection, query_cookie, &error) };
if (!xkbQueryRequestReply || error)
{
if (error)
{
AZ_Warning("XcbInput", false, "Retrieving XFixes version failed : Error code %d", error->error_code);
free(error);
}
return m_xfixesInitialized;
}
else if (xkbQueryRequestReply->major_version < 5)
{
AZ_Warning("XcbInput", false, "XFixes version fails the minimum version check (%d<5)", xkbQueryRequestReply->major_version);
return m_xfixesInitialized;
}
m_xfixesInitialized = true;
return m_xfixesInitialized;
}
bool XcbInputDeviceMouse::InitializeXInput()
{
m_xInputInitialized = false;
// We don't have to free query_extension_reply according to xcb documentation.
const xcb_query_extension_reply_t* query_extension_reply = xcb_get_extension_data(s_xcbConnection, &xcb_input_id);
if (!query_extension_reply || !query_extension_reply->present)
{
return m_xInputInitialized;
}
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;
const XcbStdFreePtr<xcb_input_xi_query_version_reply_t> xkbQueryRequestReply{ xcb_input_xi_query_version_reply(
s_xcbConnection, query_version_cookie, &error) };
if (!xkbQueryRequestReply || error)
{
if (error)
{
AZ_Warning("XcbInput", false, "Retrieving XInput version failed : Error code %d", error->error_code);
free(error);
}
return m_xInputInitialized;
}
else if (xkbQueryRequestReply->major_version < 2)
{
AZ_Warning("XcbInput", false, "XInput version fails the minimum version check (%d<5)", xkbQueryRequestReply->major_version);
return m_xInputInitialized;
}
m_xInputInitialized = true;
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();
HandleCursorState(m_focusWindow, systemCursorState);
}
}
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);
CreateBarriers(window, confined);
ShowCursor(window, cursorShown);
}
SystemCursorState XcbInputDeviceMouse::GetSystemCursorState() const
{
return m_systemCursorState;
}
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) };
if (!xkbGeometryReply)
{
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);
xcb_warp_pointer(s_xcbConnection, XCB_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)
{
return;
}
SetSystemCursorPositionNormalizedInternal(window, positionNormalized);
}
AZ::Vector2 XcbInputDeviceMouse::GetSystemCursorPositionNormalizedInternal(xcb_window_t window) const
{
AZ::Vector2 position = AZ::Vector2::CreateZero();
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) };
if (!xkbQueryPointerReply)
{
return position;
}
const XcbStdFreePtr<xcb_get_geometry_reply_t> xkbGeometryReply{ xcb_get_geometry_reply(
s_xcbConnection, xcb_get_geometry(s_xcbConnection, window), NULL) };
if (!xkbGeometryReply)
{
return position;
}
AZ_Assert(xkbGeometryReply->width != 0, "xkbGeometry response width must be non-zero. (%d)", xkbGeometryReply->width);
const float normalizedCursorPostionX = static_cast<float>(xkbQueryPointerReply->win_x) / xkbGeometryReply->width;
AZ_Assert(xkbGeometryReply->height != 0, "xkbGeometry response height must be non-zero. (%d)", xkbGeometryReply->height);
const float normalizedCursorPostionY = static_cast<float>(xkbQueryPointerReply->win_y) / xkbGeometryReply->height;
position = AZ::Vector2(normalizedCursorPostionX, normalizedCursorPostionY);
return position;
}
AZ::Vector2 XcbInputDeviceMouse::GetSystemCursorPositionNormalized() const
{
const xcb_window_t window = GetSystemCursorFocusWindow();
if (XCB_NONE == window)
{
return AZ::Vector2::CreateZero();
}
return GetSystemCursorPositionNormalizedInternal(window);
}
void XcbInputDeviceMouse::TickInputDevice()
{
ProcessRawEventQueues();
}
void XcbInputDeviceMouse::ShowCursor(xcb_window_t window, bool show)
{
xcb_void_cookie_t cookie;
if (show)
{
cookie = xcb_xfixes_show_cursor_checked(s_xcbConnection, window);
}
else
{
cookie = xcb_xfixes_hide_cursor_checked(s_xcbConnection, window);
}
const XcbStdFreePtr<xcb_generic_error_t> xkbError{ xcb_request_check(s_xcbConnection, cookie) };
if (xkbError)
{
AZ_Warning("XcbInput", false, "ShowCursor failed: %d", xkbError->error_code);
return;
}
// ATTN In the following part we will when cursor gets hidden store the position of the cursor in screen space
// not window space. We use that to re-position when showing the cursor again. Is this the correct
// behavior?
const bool cursorWasHidden = !m_cursorShown;
m_cursorShown = show;
if (!m_cursorShown)
{
m_cursorHiddenPosition = GetSystemCursorPositionNormalizedInternal(s_xcbScreen->root);
SetSystemCursorPositionNormalized(AZ::Vector2(0.5f, 0.5f));
}
else if (cursorWasHidden)
{
SetSystemCursorPositionNormalizedInternal(s_xcbScreen->root, m_cursorHiddenPosition);
}
xcb_flush(s_xcbConnection);
}
void XcbInputDeviceMouse::HandleButtonPressEvents(uint32_t detail, bool pressed)
{
bool isWheel;
float wheelDirection;
const auto* button = InputChannelFromMouseEvent(detail, isWheel, wheelDirection);
if (button)
{
QueueRawButtonEvent(*button, pressed);
}
if (isWheel)
{
float axisValue = MAX_XI_WHEEL_SENSITIVITY * wheelDirection;
QueueRawMovementEvent(InputDeviceMouse::Movement::Z, axisValue);
}
}
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);
switch (genericEvent->event_type)
{
case XCB_INPUT_RAW_BUTTON_PRESS:
{
const xcb_input_raw_button_press_event_t* mouseButtonEvent =
reinterpret_cast<const xcb_input_raw_button_press_event_t*>(event);
HandleButtonPressEvents(mouseButtonEvent->detail, true);
}
break;
case XCB_INPUT_RAW_BUTTON_RELEASE:
{
const xcb_input_raw_button_release_event_t* mouseButtonEvent =
reinterpret_cast<const xcb_input_raw_button_release_event_t*>(event);
HandleButtonPressEvents(mouseButtonEvent->detail, false);
}
break;
case XCB_INPUT_RAW_MOTION:
{
const xcb_input_raw_motion_event_t* mouseMotionEvent = reinterpret_cast<const xcb_input_raw_motion_event_t*>(event);
int axisLen = xcb_input_raw_button_press_axisvalues_length(mouseMotionEvent);
const xcb_input_fp3232_t* axisvalues = xcb_input_raw_button_press_axisvalues_raw(mouseMotionEvent);
for (int i = 0; i < axisLen; ++i)
{
const float axisValue = fp3232ToFloat(axisvalues[i]);
switch (i)
{
case 0:
QueueRawMovementEvent(InputDeviceMouse::Movement::X, axisValue);
break;
case 1:
QueueRawMovementEvent(InputDeviceMouse::Movement::Y, axisValue);
break;
}
}
}
break;
}
}
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.
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;
}
}
}
break;
case XCB_FOCUS_IN:
{
const xcb_focus_in_event_t* focusInEvent = reinterpret_cast<const xcb_focus_in_event_t*>(event);
if (m_focusWindow != focusInEvent->event)
{
m_focusWindow = focusInEvent->event;
HandleCursorState(m_focusWindow, m_systemCursorState);
}
}
break;
case XCB_FOCUS_OUT:
{
const xcb_focus_out_event_t* focusOutEvent = reinterpret_cast<const xcb_focus_out_event_t*>(event);
HandleCursorState(focusOutEvent->event, SystemCursorState::UnconstrainedAndVisible);
ProcessRawEventQueues();
ResetInputChannelStates();
m_focusWindow = XCB_NONE;
}
break;
}
}
} // namespace AzFramework
@@ -0,0 +1,193 @@
/*
* 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 <AzFramework/Input/Devices/Mouse/InputDeviceMouse.h>
#include <AzFramework/XcbConnectionManager.h>
#include <AzFramework/XcbEventHandler.h>
#include <AzFramework/XcbInterface.h>
#include <xcb/xfixes.h>
#include <xcb/xinput.h>
// The maximum number of raw input axis this mouse device supports.
constexpr uint32_t MAX_XI_RAW_AXIS = 2;
// The sensitivity of the wheel.
constexpr float MAX_XI_WHEEL_SENSITIVITY = 140.0f;
namespace AzFramework
{
class XcbInputDeviceMouse
: public InputDeviceMouse::Implementation
, public XcbEventHandlerBus::Handler
{
public:
AZ_CLASS_ALLOCATOR(XcbInputDeviceMouse, AZ::SystemAllocator, 0);
////////////////////////////////////////////////////////////////////////////////////////////
//! Constructor
//! \param[in] inputDevice Reference to the input device being implemented
XcbInputDeviceMouse(InputDeviceMouse& inputDevice);
////////////////////////////////////////////////////////////////////////////////////////////
//! Destructor
virtual ~XcbInputDeviceMouse();
static XcbInputDeviceMouse::Implementation* Create(InputDeviceMouse& inputDevice);
protected:
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceMouse::Implementation::IsConnected
bool IsConnected() const override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceMouse::Implementation::SetSystemCursorState
void SetSystemCursorState(SystemCursorState systemCursorState) override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceMouse::Implementation::GetSystemCursorState
SystemCursorState GetSystemCursorState() const override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceMouse::Implementation::SetSystemCursorPositionNormalized
void SetSystemCursorPositionNormalized(AZ::Vector2 positionNormalized) override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceMouse::Implementation::GetSystemCursorPositionNormalized
AZ::Vector2 GetSystemCursorPositionNormalized() const override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \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;
//! Initialize XFixes extension. Used for barriers.
static bool InitializeXFixes();
//! 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);
//! Helper function.
void SystemCursorStateToLogic(SystemCursorState systemCursorState, bool& confined, bool& cursorShown);
//! Shows/Hides the cursor.
void ShowCursor(xcb_window_t window, bool show);
//! Get the normalized cursor position. The coordinates returned are relative to the specified window.
AZ::Vector2 GetSystemCursorPositionNormalizedInternal(xcb_window_t window) const;
//! Set the normalized cursor position. The normalized position will be relative to the specified window.
void SetSystemCursorPositionNormalizedInternal(xcb_window_t window, AZ::Vector2 positionNormalized);
//! 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);
//! Will handle all raw input events.
void HandleRawInputEvents(const xcb_ge_generic_event_t* event);
//! Convert XInput fp1616 to float.
inline float fp1616ToFloat(xcb_input_fp1616_t value) const
{
return static_cast<float>((value >> 16) + (value & 0xffff) / 0xffff);
}
//! Convert XInput fp3232 to float.
inline float fp3232ToFloat(xcb_input_fp3232_t value) const
{
return static_cast<float>(value.integral) + static_cast<float>(value.frac / (float)(1ull << 32));
}
const InputChannelId* InputChannelFromMouseEvent(xcb_button_t button, bool& isWheel, float& direction) const
{
isWheel = false;
direction = 1.0f;
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;
case XCB_BUTTON_INDEX_4:
isWheel = true;
direction = 1.0f;
break;
case XCB_BUTTON_INDEX_5:
isWheel = true;
direction = -1.0f;
break;
default:
break;
}
return nullptr;
}
// Barriers work only with positive values. We clamp here to zero.
inline int16_t Clamp(int16_t value) const
{
return value < 0 ? 0 : value;
}
private:
//! The current system cursor state
SystemCursorState m_systemCursorState;
//! The cursor position before it got hidden.
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;
//! Will be true if the xfixes extension could be initialized.
static bool m_xfixesInitialized;
//! 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;
//! Will be true if the cursor is shown else false.
bool m_cursorShown;
struct XFixesBarrierProperty
{
xcb_xfixes_barrier_t id;
uint32_t direction;
int16_t x0, y0, x1, y1;
};
//! Array that holds barrier information used to confine the cursor.
std::vector<XFixesBarrierProperty> m_activeBarriers;
};
} // namespace AzFramework
@@ -8,25 +8,31 @@
#include <AzFramework/Application/Application.h>
#include <AzFramework/Windowing/NativeWindow.h>
#include <AzFramework/XcbNativeWindow.h>
#include <AzFramework/XcbConnectionManager.h>
#include <AzFramework/XcbInterface.h>
#include <AzFramework/XcbNativeWindow.h>
#include <xcb/xcb.h>
namespace AzFramework
{
[[maybe_unused]] const char XcbErrorWindow[] = "XcbNativeWindow";
static constexpr uint8_t s_XcbFormatDataSize = 32; // Format indicator for xcb for client messages
static constexpr uint16_t s_DefaultXcbWindowBorderWidth = 4; // The default border with in pixels if a border was specified
static constexpr uint8_t s_XcbResponseTypeMask = 0x7f; // Mask to extract the specific event type from an xcb event
static constexpr uint8_t s_XcbFormatDataSize = 32; // Format indicator for xcb for client messages
static constexpr uint16_t s_DefaultXcbWindowBorderWidth = 4; // The default border with in pixels if a border was specified
static constexpr uint8_t s_XcbResponseTypeMask = 0x7f; // Mask to extract the specific event type from an xcb event
#define _NET_WM_STATE_REMOVE 0l
#define _NET_WM_STATE_ADD 1l
#define _NET_WM_STATE_TOGGLE 2l
////////////////////////////////////////////////////////////////////////////////////////////////
XcbNativeWindow::XcbNativeWindow()
XcbNativeWindow::XcbNativeWindow()
: NativeWindow::Implementation()
, m_xcbConnection(nullptr)
, m_xcbRootScreen(nullptr)
, m_xcbWindow(XCB_NONE)
{
if (auto xcbConnectionManager = AzFramework::XcbConnectionManagerInterface::Get();
xcbConnectionManager != nullptr)
if (auto xcbConnectionManager = AzFramework::XcbConnectionManagerInterface::Get(); xcbConnectionManager != nullptr)
{
m_xcbConnection = xcbConnectionManager->GetXcbConnection();
}
@@ -34,89 +40,184 @@ namespace AzFramework
}
////////////////////////////////////////////////////////////////////////////////////////////////
XcbNativeWindow::~XcbNativeWindow() = default;
XcbNativeWindow::~XcbNativeWindow()
{
if (XCB_NONE != m_xcbWindow)
{
xcb_destroy_window(m_xcbConnection, m_xcbWindow);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
void XcbNativeWindow::InitWindow(const AZStd::string& title,
const WindowGeometry& geometry,
const WindowStyleMasks& styleMasks)
void XcbNativeWindow::InitWindow(const AZStd::string& title, const WindowGeometry& geometry, const WindowStyleMasks& styleMasks)
{
// Get the parent window
// Get the parent window
const xcb_setup_t* xcbSetup = xcb_get_setup(m_xcbConnection);
xcb_screen_t* xcbRootScreen = xcb_setup_roots_iterator(xcbSetup).data;
xcb_window_t xcbParentWindow = xcbRootScreen->root;
m_xcbRootScreen = xcb_setup_roots_iterator(xcbSetup).data;
xcb_window_t xcbParentWindow = m_xcbRootScreen->root;
// Create an XCB window from the connection
m_xcbWindow = xcb_generate_id(m_xcbConnection);
uint16_t borderWidth = 0;
const uint32_t mask = styleMasks.m_platformAgnosticStyleMask;
if ((mask & WindowStyleMasks::WINDOW_STYLE_BORDERED) ||
(mask & WindowStyleMasks::WINDOW_STYLE_RESIZEABLE))
if ((mask & WindowStyleMasks::WINDOW_STYLE_BORDERED) || (mask & WindowStyleMasks::WINDOW_STYLE_RESIZEABLE))
{
borderWidth = s_DefaultXcbWindowBorderWidth;
}
uint32_t eventMask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK;
const uint32_t interestedEvents =
XCB_EVENT_MASK_STRUCTURE_NOTIFY
| XCB_EVENT_MASK_BUTTON_PRESS
| XCB_EVENT_MASK_BUTTON_RELEASE
| XCB_EVENT_MASK_KEY_PRESS
| XCB_EVENT_MASK_KEY_RELEASE
| XCB_EVENT_MASK_POINTER_MOTION
;
uint32_t valueList[] = { xcbRootScreen->black_pixel,
interestedEvents };
const uint32_t interestedEvents = XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE |
XCB_EVENT_MASK_FOCUS_CHANGE | XCB_EVENT_MASK_PROPERTY_CHANGE;
uint32_t valueList[] = { m_xcbRootScreen->black_pixel, interestedEvents };
xcb_void_cookie_t xcbCheckResult;
xcbCheckResult = xcb_create_window_checked(m_xcbConnection,
XCB_COPY_FROM_PARENT,
m_xcbWindow,
xcbParentWindow,
aznumeric_cast<int16_t>(geometry.m_posX),
aznumeric_cast<int16_t>(geometry.m_posY),
aznumeric_cast<int16_t>(geometry.m_width),
aznumeric_cast<int16_t>(geometry.m_height),
borderWidth,
XCB_WINDOW_CLASS_INPUT_OUTPUT,
xcbRootScreen->root_visual,
eventMask,
valueList);
xcbCheckResult = xcb_create_window_checked(
m_xcbConnection, XCB_COPY_FROM_PARENT, m_xcbWindow, xcbParentWindow, aznumeric_cast<int16_t>(geometry.m_posX),
aznumeric_cast<int16_t>(geometry.m_posY), aznumeric_cast<int16_t>(geometry.m_width), aznumeric_cast<int16_t>(geometry.m_height),
borderWidth, XCB_WINDOW_CLASS_INPUT_OUTPUT, m_xcbRootScreen->root_visual, eventMask, valueList);
AZ_Assert(ValidateXcbResult(xcbCheckResult), "Failed to create xcb window.");
SetWindowTitle(title);
// Setup the window close event
const static char* wmProtocolString = "WM_PROTOCOLS";
xcb_intern_atom_cookie_t cookieProtocol = xcb_intern_atom(m_xcbConnection, 1, strlen(wmProtocolString), wmProtocolString);
xcb_intern_atom_reply_t* replyProtocol = xcb_intern_atom_reply(m_xcbConnection, cookieProtocol, nullptr);
AZ_Error(XcbErrorWindow, replyProtocol != nullptr, "Unable to query xcb '%s' atom", wmProtocolString);
m_xcbAtomProtocols = replyProtocol->atom;
const static char* wmDeleteWindowString = "WM_DELETE_WINDOW";
xcb_intern_atom_cookie_t cookieDeleteWindow = xcb_intern_atom(m_xcbConnection, 0, strlen(wmDeleteWindowString), wmDeleteWindowString);
xcb_intern_atom_reply_t* replyDeleteWindow = xcb_intern_atom_reply(m_xcbConnection, cookieDeleteWindow, nullptr);
AZ_Error(XcbErrorWindow, replyDeleteWindow != nullptr, "Unable to query xcb '%s' atom", wmDeleteWindowString);
m_xcbAtomDeleteWindow = replyDeleteWindow->atom;
xcbCheckResult = xcb_change_property_checked(m_xcbConnection,
XCB_PROP_MODE_REPLACE,
m_xcbWindow,
m_xcbAtomProtocols,
XCB_ATOM_ATOM,
s_XcbFormatDataSize,
1,
&m_xcbAtomDeleteWindow);
AZ_Assert(ValidateXcbResult(xcbCheckResult), "Failed to change the xcb atom property for WM_CLOSE event");
m_posX = geometry.m_posX;
m_posY = geometry.m_posY;
m_width = geometry.m_width;
m_height = geometry.m_height;
InitializeAtoms();
xcb_client_message_event_t event;
event.response_type = XCB_CLIENT_MESSAGE;
event.type = _NET_REQUEST_FRAME_EXTENTS;
event.window = m_xcbWindow;
event.format = 32;
event.sequence = 0;
event.data.data32[0] = 0l;
event.data.data32[1] = 0l;
event.data.data32[2] = 0l;
event.data.data32[3] = 0l;
event.data.data32[4] = 0l;
xcbCheckResult = xcb_send_event(
m_xcbConnection, 1, m_xcbRootScreen->root, XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT,
(const char*)&event);
AZ_Assert(ValidateXcbResult(xcbCheckResult), "Failed to set _NET_REQUEST_FRAME_EXTENTS");
// The WM will be able to kill the application if it gets unresponsive.
int32_t pid = getpid();
xcb_change_property(m_xcbConnection, XCB_PROP_MODE_REPLACE, m_xcbWindow, _NET_WM_PID, XCB_ATOM_CARDINAL, 32, 1, &pid);
xcb_flush(m_xcbConnection);
}
xcb_atom_t XcbNativeWindow::GetAtom(const char* atomName)
{
xcb_intern_atom_cookie_t intern_atom_cookie = xcb_intern_atom(m_xcbConnection, 0, strlen(atomName), atomName);
XcbStdFreePtr<xcb_intern_atom_reply_t> xkbinternAtom{ xcb_intern_atom_reply(m_xcbConnection, intern_atom_cookie, NULL) };
if (!xkbinternAtom)
{
AZ_Error(XcbErrorWindow, xkbinternAtom != nullptr, "Unable to query xcb '%s' atom", atomName);
return XCB_NONE;
}
return xkbinternAtom->atom;
}
int XcbNativeWindow::SetAtom(xcb_window_t window, xcb_atom_t atom, xcb_atom_t type, size_t len, void* data)
{
xcb_void_cookie_t cookie = xcb_change_property_checked(m_xcbConnection, XCB_PROP_MODE_REPLACE, window, atom, type, 32, len, data);
XcbStdFreePtr<xcb_generic_error_t> xkbError{ xcb_request_check(m_xcbConnection, cookie) };
if (!xkbError)
{
return 0;
}
return xkbError->error_code;
}
////////////////////////////////////////////////////////////////////////////////////////////////
void XcbNativeWindow::InitializeAtoms()
{
AZStd::vector<xcb_atom_t> Atoms;
_NET_ACTIVE_WINDOW = GetAtom("_NET_ACTIVE_WINDOW");
_NET_WM_BYPASS_COMPOSITOR = GetAtom("_NET_WM_BYPASS_COMPOSITOR");
// ---------------------------------------------------------------------
// Handle all WM Protocols atoms.
//
WM_PROTOCOLS = GetAtom("WM_PROTOCOLS");
// This atom is used to close a window. Emitted when user clicks the close button.
WM_DELETE_WINDOW = GetAtom("WM_DELETE_WINDOW");
Atoms.push_back(WM_DELETE_WINDOW);
xcb_change_property(
m_xcbConnection, XCB_PROP_MODE_REPLACE, m_xcbWindow, WM_PROTOCOLS, XCB_ATOM_ATOM, 32, Atoms.size(), Atoms.data());
xcb_flush(m_xcbConnection);
// ---------------------------------------------------------------------
// Handle all WM State atoms.
//
_NET_WM_STATE = GetAtom("_NET_WM_STATE");
_NET_WM_STATE_FULLSCREEN = GetAtom("_NET_WM_STATE_FULLSCREEN");
_NET_WM_STATE_MAXIMIZED_VERT = GetAtom("_NET_WM_STATE_MAXIMIZED_VERT");
_NET_WM_STATE_MAXIMIZED_HORZ = GetAtom("_NET_WM_STATE_MAXIMIZED_HORZ");
_NET_MOVERESIZE_WINDOW = GetAtom("_NET_MOVERESIZE_WINDOW");
_NET_REQUEST_FRAME_EXTENTS = GetAtom("_NET_REQUEST_FRAME_EXTENTS");
_NET_FRAME_EXTENTS = GetAtom("_NET_FRAME_EXTENTS");
_NET_WM_PID = GetAtom("_NET_WM_PID");
}
void XcbNativeWindow::GetWMStates()
{
xcb_get_property_cookie_t cookie = xcb_get_property(m_xcbConnection, 0, m_xcbWindow, _NET_WM_STATE, XCB_ATOM_ATOM, 0, 1024);
xcb_generic_error_t* error = nullptr;
XcbStdFreePtr<xcb_get_property_reply_t> xkbGetPropertyReply{ xcb_get_property_reply(m_xcbConnection, cookie, &error) };
if (!xkbGetPropertyReply || error || !((xkbGetPropertyReply->format == 32) && (xkbGetPropertyReply->type == XCB_ATOM_ATOM)))
{
AZ_Warning("ApplicationLinux", false, "Acquiring _NET_WM_STATE information from the WM failed.");
if (error)
{
AZ_TracePrintf("Error", "Error code %d", error->error_code);
free(error);
}
return;
}
m_fullscreenState = false;
m_horizontalyMaximized = false;
m_verticallyMaximized = false;
const xcb_atom_t* states = static_cast<const xcb_atom_t*>(xcb_get_property_value(xkbGetPropertyReply.get()));
for (int i = 0; i < xkbGetPropertyReply->length; i++)
{
if (states[i] == _NET_WM_STATE_FULLSCREEN)
{
m_fullscreenState = true;
}
else if (states[i] == _NET_WM_STATE_MAXIMIZED_HORZ)
{
m_horizontalyMaximized = true;
}
else if (states[i] == _NET_WM_STATE_MAXIMIZED_VERT)
{
m_verticallyMaximized = true;
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
@@ -146,7 +247,7 @@ namespace AzFramework
xcb_flush(m_xcbConnection);
}
XcbEventHandlerBus::Handler::BusDisconnect();
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
NativeWindowHandle XcbNativeWindow::GetWindowHandle() const
@@ -158,14 +259,9 @@ namespace AzFramework
void XcbNativeWindow::SetWindowTitle(const AZStd::string& title)
{
xcb_void_cookie_t xcbCheckResult;
xcbCheckResult = xcb_change_property(m_xcbConnection,
XCB_PROP_MODE_REPLACE,
m_xcbWindow,
XCB_ATOM_WM_NAME,
XCB_ATOM_STRING,
8,
static_cast<uint32_t>(title.size()),
title.c_str());
xcbCheckResult = xcb_change_property(
m_xcbConnection, XCB_PROP_MODE_REPLACE, m_xcbWindow, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8, static_cast<uint32_t>(title.size()),
title.c_str());
AZ_Assert(ValidateXcbResult(xcbCheckResult), "Failed to set window title.");
}
@@ -175,7 +271,7 @@ namespace AzFramework
const uint32_t values[] = { clientAreaSize.m_width, clientAreaSize.m_height };
xcb_configure_window(m_xcbConnection, m_xcbWindow, XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, values);
m_width = clientAreaSize.m_width;
m_height = clientAreaSize.m_height;
}
@@ -185,16 +281,77 @@ namespace AzFramework
{
// [GFX TODO][GHI - 2678]
// Using 60 for now until proper support is added
return 60;
}
bool XcbNativeWindow::GetFullScreenState() const
{
return m_fullscreenState;
}
void XcbNativeWindow::SetFullScreenState(bool fullScreenState)
{
// TODO This is a pretty basic full-screen implementation using WM's _NET_WM_STATE_FULLSCREEN state.
// Do we have to provide also the old way?
GetWMStates();
xcb_client_message_event_t event;
event.response_type = XCB_CLIENT_MESSAGE;
event.type = _NET_WM_STATE;
event.window = m_xcbWindow;
event.format = 32;
event.sequence = 0;
event.data.data32[0] = fullScreenState ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
event.data.data32[1] = _NET_WM_STATE_FULLSCREEN;
event.data.data32[2] = 0;
event.data.data32[3] = 1;
event.data.data32[4] = 0;
xcb_void_cookie_t xcbCheckResult = xcb_send_event(
m_xcbConnection, 1, m_xcbRootScreen->root, XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT,
(const char*)&event);
AZ_Assert(ValidateXcbResult(xcbCheckResult), "Failed to set _NET_WM_STATE_FULLSCREEN");
// Also try to disable/enable the compositor if possible. Might help in some cases.
const long _NET_WM_BYPASS_COMPOSITOR_HINT_ON = m_fullscreenState ? 1 : 0;
SetAtom(m_xcbWindow, _NET_WM_BYPASS_COMPOSITOR, XCB_ATOM_CARDINAL, 32, (char*)&_NET_WM_BYPASS_COMPOSITOR_HINT_ON);
if (!fullScreenState)
{
if (m_horizontalyMaximized || m_verticallyMaximized)
{
printf("Remove maximized state.\n");
xcb_client_message_event_t event;
event.response_type = XCB_CLIENT_MESSAGE;
event.type = _NET_WM_STATE;
event.window = m_xcbWindow;
event.format = 32;
event.sequence = 0;
event.data.data32[0] = _NET_WM_STATE_MAXIMIZED_VERT;
event.data.data32[1] = _NET_WM_STATE_MAXIMIZED_HORZ;
event.data.data32[2] = 0;
event.data.data32[3] = 0;
event.data.data32[4] = 0;
xcb_void_cookie_t xcbCheckResult = xcb_send_event(
m_xcbConnection, 1, m_xcbRootScreen->root, XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT,
(const char*)&event);
AZ_Assert(
ValidateXcbResult(xcbCheckResult), "Failed to remove _NET_WM_STATE_MAXIMIZED_VERT | _NET_WM_STATE_MAXIMIZED_HORZ");
}
}
xcb_flush(m_xcbConnection);
m_fullscreenState = fullScreenState;
}
////////////////////////////////////////////////////////////////////////////////////////////////
bool XcbNativeWindow::ValidateXcbResult(xcb_void_cookie_t cookie)
{
bool result = true;
if (xcb_generic_error_t* error = xcb_request_check(m_xcbConnection, cookie))
{
AZ_TracePrintf("Error","Error code %d", error->error_code);
AZ_TracePrintf("Error", "Error code %d", error->error_code);
result = false;
}
return result;
@@ -205,20 +362,20 @@ namespace AzFramework
{
switch (event->response_type & s_XcbResponseTypeMask)
{
case XCB_CONFIGURE_NOTIFY:
case XCB_CONFIGURE_NOTIFY:
{
xcb_configure_notify_event_t* cne = reinterpret_cast<xcb_configure_notify_event_t*>(event);
WindowSizeChanged(aznumeric_cast<uint32_t>(cne->width),
aznumeric_cast<uint32_t>(cne->height));
if ((cne->width != m_width) || (cne->height != m_height))
{
WindowSizeChanged(aznumeric_cast<uint32_t>(cne->width), aznumeric_cast<uint32_t>(cne->height));
}
break;
}
case XCB_CLIENT_MESSAGE:
case XCB_CLIENT_MESSAGE:
{
xcb_client_message_event_t* cme = reinterpret_cast<xcb_client_message_event_t*>(event);
if ((cme->type == m_xcbAtomProtocols) &&
(cme->format == s_XcbFormatDataSize) &&
(cme->data.data32[0] == m_xcbAtomDeleteWindow))
if ((cme->type == WM_PROTOCOLS) && (cme->format == s_XcbFormatDataSize) && (cme->data.data32[0] == WM_DELETE_WINDOW))
{
Deactivate();
@@ -239,7 +396,8 @@ namespace AzFramework
if (m_activated)
{
WindowNotificationBus::Event(reinterpret_cast<NativeWindowHandle>(m_xcbWindow), &WindowNotificationBus::Events::OnWindowResized, width, height);
WindowNotificationBus::Event(
reinterpret_cast<NativeWindowHandle>(m_xcbWindow), &WindowNotificationBus::Events::OnWindowResized, width, height);
}
}
}
@@ -27,15 +27,16 @@ namespace AzFramework
////////////////////////////////////////////////////////////////////////////////////////////
// NativeWindow::Implementation
void InitWindow(const AZStd::string& title,
const WindowGeometry& geometry,
const WindowStyleMasks& styleMasks) override;
void InitWindow(const AZStd::string& title, const WindowGeometry& geometry, const WindowStyleMasks& styleMasks) override;
void Activate() override;
void Deactivate() override;
NativeWindowHandle GetWindowHandle() const override;
void SetWindowTitle(const AZStd::string& title) override;
void ResizeClientArea(WindowSize clientAreaSize) override;
uint32_t GetDisplayRefreshRate() const override;
uint32_t GetDisplayRefreshRate() const override;
bool GetFullScreenState() const override;
void SetFullScreenState(bool fullScreenState) override;
////////////////////////////////////////////////////////////////////////////////////////////
// XcbEventHandlerBus::Handler
@@ -44,10 +45,46 @@ namespace AzFramework
private:
bool ValidateXcbResult(xcb_void_cookie_t cookie);
void WindowSizeChanged(const uint32_t width, const uint32_t height);
int SetAtom(xcb_window_t window, xcb_atom_t atom, xcb_atom_t type, size_t len, void* data);
xcb_connection_t* m_xcbConnection = nullptr;
xcb_window_t m_xcbWindow = 0;
xcb_atom_t m_xcbAtomProtocols;
xcb_atom_t m_xcbAtomDeleteWindow;
// Initialize one atom.
xcb_atom_t GetAtom(const char* atomName);
// Initialize all used atoms.
void InitializeAtoms();
void GetWMStates();
xcb_connection_t* m_xcbConnection = nullptr;
xcb_screen_t* m_xcbRootScreen = nullptr;
xcb_window_t m_xcbWindow = 0;
int32_t m_posX;
int32_t m_posY;
bool m_fullscreenState = false;
bool m_horizontalyMaximized = false;
bool m_verticallyMaximized = false;
// Use exact atom names for easy readability and usage.
xcb_atom_t WM_PROTOCOLS;
xcb_atom_t WM_DELETE_WINDOW;
// This atom is used to activate a window.
xcb_atom_t _NET_ACTIVE_WINDOW;
// This atom is use to bypass a compositor. Used during fullscreen mode.
xcb_atom_t _NET_WM_BYPASS_COMPOSITOR;
// This atom is used to change the state of a window using the WM.
xcb_atom_t _NET_WM_STATE;
// This atom is used to enable/disable fullscreen mode of a window.
xcb_atom_t _NET_WM_STATE_FULLSCREEN;
// This atom is used to extend the window to max vertically.
xcb_atom_t _NET_WM_STATE_MAXIMIZED_VERT;
// This atom is used to extend the window to max horizontally.
xcb_atom_t _NET_WM_STATE_MAXIMIZED_HORZ;
// This atom is used to position and resize a window.
xcb_atom_t _NET_MOVERESIZE_WINDOW;
// This atom is used to request the extent of the window.
xcb_atom_t _NET_REQUEST_FRAME_EXTENTS;
// This atom is used to identify the reply event for _NET_REQUEST_FRAME_EXTENTS
xcb_atom_t _NET_FRAME_EXTENTS;
// This atom is used to allow WM to kill app if not responsive anymore
xcb_atom_t _NET_WM_PID;
};
} // namespace AzFramework
@@ -12,6 +12,8 @@ set(FILES
AzFramework/XcbConnectionManager.h
AzFramework/XcbInputDeviceKeyboard.cpp
AzFramework/XcbInputDeviceKeyboard.h
AzFramework/XcbInputDeviceMouse.cpp
AzFramework/XcbInputDeviceMouse.h
AzFramework/XcbInterface.h
AzFramework/XcbNativeWindow.cpp
AzFramework/XcbNativeWindow.h
@@ -0,0 +1,27 @@
/*
* 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
*
*/
#if PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB
#include <AzFramework/XcbInputDeviceMouse.h>
#endif
namespace AzFramework
{
InputDeviceMouse::Implementation* InputDeviceMouse::Implementation::Create(InputDeviceMouse& inputDevice)
{
#if PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB
return XcbInputDeviceMouse::Create(inputDevice);
#elif PAL_TRAIT_LINUX_WINDOW_MANAGER_WAYLAND
#error "Linux Window Manager Wayland not supported."
return nullptr;
#else
#error "Linux Window Manager not recognized."
return nullptr;
#endif // PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB
}
} // namespace AzFramework
@@ -22,8 +22,10 @@ if (${PAL_TRAIT_LINUX_WINDOW_MANAGER} STREQUAL "xcb")
PRIVATE
3rdParty::X11::xcb
3rdParty::X11::xcb_xkb
3rdParty::X11::xcb_xfixes
3rdParty::X11::xkbcommon
3rdParty::X11::xkbcommon_X11
xcb-xinput
)
elseif(PAL_TRAIT_LINUX_WINDOW_MANAGER STREQUAL "wayland")
@@ -22,8 +22,8 @@ set(FILES
AzFramework/Windowing/NativeWindow_Linux.cpp
../Common/Unimplemented/AzFramework/Input/Devices/Gamepad/InputDeviceGamepad_Unimplemented.cpp
AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard_Linux.cpp
AzFramework/Input/Devices/Mouse/InputDeviceMouse_Linux.cpp
../Common/Unimplemented/AzFramework/Input/Devices/Motion/InputDeviceMotion_Unimplemented.cpp
../Common/Unimplemented/AzFramework/Input/Devices/Mouse/InputDeviceMouse_Unimplemented.cpp
../Common/Unimplemented/AzFramework/Input/Devices/Touch/InputDeviceTouch_Unimplemented.cpp
AzFramework/Input/User/LocalUserId_Platform.h
../Common/Default/AzFramework/Input/User/LocalUserId_Default.h