Prechádzať zdrojové kódy

Add workaround for key repeat issue on x11 (#18893)

Signed-off-by: Yaakuro <[email protected]>
Yaakuro 1 mesiac pred
rodič
commit
d131f74691

+ 2 - 0
Code/Editor/Platform/Linux/Editor/Core/QtEditorApplication_linux.cpp

@@ -49,6 +49,8 @@ namespace Editor
     {
         auto* interface = AzFramework::XcbConnectionManagerInterface::Get();
         interface->SetEnableXInput(GetXcbConnectionFromQt(), false);
+
+        AzFramework::XcbEventHandlerBus::Broadcast(&AzFramework::XcbEventHandler::ResetStoredInputStates);
     }
 
     bool EditorQtApplicationXcb::nativeEventFilter([[maybe_unused]] const QByteArray& eventType, void* message, long*)

+ 3 - 0
Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbEventHandler.h

@@ -25,6 +25,9 @@ namespace AzFramework
         virtual ~XcbEventHandler() = default;
 
         virtual void HandleXcbEvent(xcb_generic_event_t* event) = 0;
+
+        // Resets previous keyboard, mouse, joystick, etc. events.
+        virtual void ResetStoredInputStates() {};
     };
 
     class XcbEventHandlerBusTraits : public AZ::EBusTraits

+ 52 - 25
Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbInputDeviceKeyboard.cpp

@@ -41,15 +41,15 @@ namespace AzFramework
             return;
         }
 
-        auto* connection = interface->GetXcbConnection();
-        if (!connection)
+        m_connection = interface->GetXcbConnection();
+        if (!m_connection)
         {
             AZ_Warning("ApplicationLinux", false, "XCB connection not available");
             return;
         }
 
         int initializeXkbExtensionSuccess = xkb_x11_setup_xkb_extension(
-            connection,
+            m_connection,
             1,
             0,
             XKB_X11_SETUP_XKB_EXTENSION_NO_FLAGS,
@@ -65,11 +65,11 @@ namespace AzFramework
             return;
         }
 
-        m_coreDeviceId = xkb_x11_get_core_keyboard_device_id(connection);
+        m_coreDeviceId = xkb_x11_get_core_keyboard_device_id(m_connection);
 
         m_xkbContext.reset(xkb_context_new(XKB_CONTEXT_NO_FLAGS));
-        m_xkbKeymap.reset(xkb_x11_keymap_new_from_device(m_xkbContext.get(), connection, m_coreDeviceId, XKB_KEYMAP_COMPILE_NO_FLAGS));
-        m_xkbState.reset(xkb_x11_state_new_from_device(m_xkbKeymap.get(), connection, m_coreDeviceId));
+        m_xkbKeymap.reset(xkb_x11_keymap_new_from_device(m_xkbContext.get(), m_connection, m_coreDeviceId, XKB_KEYMAP_COMPILE_NO_FLAGS));
+        m_xkbState.reset(xkb_x11_state_new_from_device(m_xkbKeymap.get(), m_connection, m_coreDeviceId));
 
         const uint16_t affectMap =
             XCB_XKB_MAP_PART_KEY_TYPES
@@ -89,9 +89,9 @@ namespace AzFramework
             ;
 
         XcbStdFreePtr<xcb_generic_error_t> error{xcb_request_check(
-            connection,
+            m_connection,
             xcb_xkb_select_events(
-                connection,
+                m_connection,
                 /* deviceSpec = */ XCB_XKB_ID_USE_CORE_KBD,
                 /* affectWhich = */ selectedEvents,
                 /* clear = */ 0,
@@ -108,9 +108,14 @@ namespace AzFramework
             return;
         }
 
+        m_lastKeysStates.fill(0);
         m_initialized = true;
     }
 
+    XcbInputDeviceKeyboard::~XcbInputDeviceKeyboard()
+    {
+    }
+
     bool XcbInputDeviceKeyboard::IsConnected() const
     {
         auto* connection = AzFramework::XcbConnectionManagerInterface::Get()->GetXcbConnection();
@@ -137,6 +142,11 @@ namespace AzFramework
         ProcessRawEventQueues();
     }
 
+    void XcbInputDeviceKeyboard::ResetStoredInputStates()
+    {
+        m_lastKeysStates.fill(0);
+    }
+
     void XcbInputDeviceKeyboard::HandleXcbEvent(xcb_generic_event_t* event)
     {
         if (!m_initialized)
@@ -145,9 +155,10 @@ namespace AzFramework
         }
 
         const auto responseType = event->response_type & ~0x80;
-        if (responseType == XCB_KEY_PRESS)
+        if ( (responseType == XCB_KEY_PRESS) || (responseType == XCB_KEY_RELEASE))
         {
             const auto* keyPress = reinterpret_cast<xcb_key_press_event_t*>(event);
+            if ((responseType == XCB_KEY_PRESS))
             {
                 auto text = TextFromKeycode(m_xkbState.get(), keyPress->detail);
                 if (!text.empty())
@@ -156,19 +167,35 @@ namespace AzFramework
                 }
             }
 
-            if (const InputChannelId* key = InputChannelFromKeyEvent(keyPress->detail))
+            const xcb_query_keymap_cookie_t cookie = xcb_query_keymap(m_connection);
+            if (xcb_query_keymap_reply_t* const reply = xcb_query_keymap_reply(m_connection, cookie, nullptr))
             {
-                QueueRawKeyEvent(*key, true);
-            }
-        }
-        else if (responseType == XCB_KEY_RELEASE)
-        {
-            const auto* keyRelease = reinterpret_cast<xcb_key_release_event_t*>(event);
+                const AZ::u8 keycode = keyPress->detail;
+                const AZ::u8 byte = keycode / 8;
+                const AZ::u8 bit = keycode % 8;
+                const AZ::u8 keyMask = 1 << bit;
+                const bool keyState = (reply->keys[byte] & keyMask) != 0;
+                const AZ::u8 lastByte = m_lastKeysStates[byte];
+                const bool lastState = (lastByte & keyMask) != 0;
+
+                if (keyState != lastState)
+                {
+                    if (const InputChannelId* key = InputChannelFromKeyEvent(keycode))
+                    {
+                        QueueRawKeyEvent(*key, keyState);
+                    }
+
+                    keyState ? m_lastKeysStates[byte] |= keyMask : m_lastKeysStates[byte] &= ~keyMask;
+                }
 
-            const InputChannelId* key = InputChannelFromKeyEvent(keyRelease->detail);
-            if (key)
+                free(reply);
+            }
+            else
             {
-                QueueRawKeyEvent(*key, false);
+                if (const InputChannelId* key = InputChannelFromKeyEvent(keyPress->detail))
+                {
+                    QueueRawKeyEvent(*key, responseType == XCB_KEY_PRESS);
+                }
             }
         }
         else if (responseType == m_xkbEventCode)
@@ -176,12 +203,12 @@ namespace AzFramework
             const auto* xkbEvent = reinterpret_cast<XcbXkbGenericEventT*>(event);
             switch (xkbEvent->xkbType)
             {
-            case XCB_XKB_STATE_NOTIFY:
-            {
-                const auto* stateNotifyEvent = reinterpret_cast<xcb_xkb_state_notify_event_t*>(event);
-                UpdateState(stateNotifyEvent);
-                break;
-            }
+                case XCB_XKB_STATE_NOTIFY:
+                {
+                    const auto* stateNotifyEvent = reinterpret_cast<xcb_xkb_state_notify_event_t*>(event);
+                    UpdateState(stateNotifyEvent);
+                    break;
+                }
             }
         }
     }

+ 5 - 0
Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbInputDeviceKeyboard.h

@@ -13,6 +13,7 @@
 
 #include <xcb/xcb.h>
 #include <xkbcommon/xkbcommon.h>
+#include <xcb/xcb_keysyms.h>
 
 struct xcb_xkb_state_notify_event_t;
 
@@ -27,6 +28,7 @@ namespace AzFramework
 
         using InputDeviceKeyboard::Implementation::Implementation;
         XcbInputDeviceKeyboard(InputDeviceKeyboard& inputDevice);
+        ~XcbInputDeviceKeyboard() override;
 
         bool IsConnected() const override;
 
@@ -36,6 +38,7 @@ namespace AzFramework
         void TickInputDevice() override;
 
         void HandleXcbEvent(xcb_generic_event_t* event) override;
+        void ResetStoredInputStates() override;
 
     private:
         [[nodiscard]] const InputChannelId* InputChannelFromKeyEvent(xcb_keycode_t code) const;
@@ -51,5 +54,7 @@ namespace AzFramework
         uint8_t m_xkbEventCode{0};
         bool m_initialized{false};
         bool m_hasTextEntryStarted{false};
+        xcb_connection_t* m_connection = nullptr;
+        AZStd::array<uint8_t, 32> m_lastKeysStates;
     };
 } // namespace AzFramework

+ 1 - 0
Code/Framework/AzFramework/Platform/Linux/platform_nativeui_linux.cmake

@@ -27,6 +27,7 @@ if (${PAL_TRAIT_LINUX_WINDOW_MANAGER} STREQUAL "xcb")
             3rdParty::X11::xkbcommon
             3rdParty::X11::xkbcommon_X11
             xcb-xinput
+            xcb-keysyms
     )
 
 elseif(PAL_TRAIT_LINUX_WINDOW_MANAGER STREQUAL "wayland")

+ 10 - 0
Code/Framework/AzFramework/Tests/Platform/Common/Xcb/MockXcbInterface.cpp

@@ -109,6 +109,16 @@ uint32_t xcb_generate_id(xcb_connection_t *c)
     return MockXcbInterface::Instance()->xcb_generate_id(c);
 }
 
+xcb_query_keymap_cookie_t xcb_query_keymap(xcb_connection_t* c)
+{
+    return MockXcbInterface::Instance()->xcb_query_keymap(c);
+}
+
+xcb_query_keymap_reply_t* xcb_query_keymap_reply(xcb_connection_t* c, xcb_query_keymap_cookie_t cookie, xcb_generic_error_t** e)
+{
+    return MockXcbInterface::Instance()->xcb_query_keymap_reply(c, cookie, e);
+}
+
 // ----------------------------------------------------------------------------
 // xcb-xkb
 xcb_xkb_use_extension_cookie_t xcb_xkb_use_extension(xcb_connection_t* c, uint16_t wantedMajor, uint16_t wantedMinor)

+ 5 - 0
Code/Framework/AzFramework/Tests/Platform/Common/Xcb/MockXcbInterface.h

@@ -95,6 +95,8 @@ public:
     MOCK_CONST_METHOD3(xcb_get_property_reply, xcb_get_property_reply_t*(xcb_connection_t* c, xcb_get_property_cookie_t cookie, xcb_generic_error_t** e));
     MOCK_CONST_METHOD1(xcb_get_property_value, void*(const xcb_get_property_reply_t* R));
     MOCK_CONST_METHOD1(xcb_generate_id, uint32_t(xcb_connection_t *c));
+    MOCK_CONST_METHOD1(xcb_query_keymap, xcb_query_keymap_cookie_t(xcb_connection_t* c));
+    MOCK_CONST_METHOD3(xcb_query_keymap_reply, xcb_query_keymap_reply_t*(xcb_connection_t* c, xcb_query_keymap_cookie_t cookie, xcb_generic_error_t** e));
 
     // xcb-xkb
     MOCK_CONST_METHOD3(xcb_xkb_use_extension, xcb_xkb_use_extension_cookie_t(xcb_connection_t* c, uint16_t wantedMajor, uint16_t wantedMinor));
@@ -144,6 +146,9 @@ public:
     MOCK_CONST_METHOD1(xcb_input_raw_button_press_axisvalues_raw, xcb_input_fp3232_t*(const xcb_input_raw_button_press_event_t* R));
     MOCK_CONST_METHOD1(xcb_input_raw_button_press_valuator_mask, uint32_t*(const xcb_input_raw_button_press_event_t* R));
 
+
+
+
 private:
     static inline MockXcbInterface* self = nullptr;
 };

+ 1 - 0
cmake/Platform/Linux/Packaging_linux.cmake

@@ -49,6 +49,7 @@ elseif("$ENV{O3DE_PACKAGE_TYPE}" STREQUAL "DEB")
         libxcb-xfixes0-dev                      # For mouse input
         libxcb-xinput-dev                       # For mouse input
         libxcb-randr0-dev                       # For xcb display
+        libxcb-keysyms1-dev
         libpcre2-16-0
         zlib1g-dev
         mesa-common-dev

+ 1 - 0
scripts/build/build_node/Platform/Linux/package-list.ubuntu-bionic.txt

@@ -22,6 +22,7 @@ libxcb-xfixes0-dev                      # For mouse input
 libxcb-xinput-dev                       # For mouse input
 libxcb-image0-dev                       # For xcb image support
 libxcb-randr0-dev                       # For xcb display info
+libxcb-keysyms1-dev                     # For xcb query keymap
 xxd
 zlib1g-dev
 mesa-common-dev

+ 1 - 0
scripts/build/build_node/Platform/Linux/package-list.ubuntu-focal.txt

@@ -21,6 +21,7 @@ libxcb-xfixes0-dev                      # For mouse input
 libxcb-xinput-dev                       # For mouse input
 libxcb-image0-dev                       # For xcb image support
 libxcb-randr0-dev                       # For xcb display info
+libxcb-keysyms1-dev                     # For xcb query keymap
 xxd
 zlib1g-dev
 mesa-common-dev

+ 1 - 0
scripts/build/build_node/Platform/Linux/package-list.ubuntu-jammy.txt

@@ -21,6 +21,7 @@ libxcb-xfixes0-dev                      # For mouse input
 libxcb-xinput-dev                       # For mouse input
 libxcb-image0-dev                       # For xcb image support
 libxcb-randr0-dev                       # For xcb display info
+libxcb-keysyms1-dev                     # For xcb query keymap
 xxd
 zlib1g-dev
 mesa-common-dev

+ 1 - 0
scripts/build/build_node/Platform/Linux/package-list.ubuntu-noble.txt

@@ -20,6 +20,7 @@ libxkbcommon-dev                        # For xcb keyboard input
 libxcb-xfixes0-dev                      # For mouse input
 libxcb-xinput-dev                       # For mouse input
 libxcb-randr0-dev                       # For xcb display info
+libxcb-keysyms1-dev                     # For xcb query keymap
 zlib1g-dev
 mesa-common-dev
 ros-jazzy-ackermann-msgs               # ROS2 development tools