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:
@@ -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
|
||||
|
||||
+27
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user