Переглянути джерело

Barrier (formerly Synergy) Input Gem (#2336)

Synergy (now Barrier) input was lost when we removed the CryLegacy Gem. This resurrects the code, updates it to work with Barrier (which is the open source project based on the original Synergy core), and updates it to be more conformant with the AzFramework Input framework.

Notes:

- The majority of code in BarrierInputClient.cpp is still largely unmodified from the original Cry drop and could use some love to make it more robust, but it works so at least gives us somewhere to start.
- The current iteration replaces the host platform's default mouse/keyboard implementation upon creation of the BarrierInputClient, and if the connection fails or is later lost we don't restore the default implementations which we should for a better use experience. On a related note, for some use cases it would be better to create additional mouse/keyboard input devices (that use the Barrier implementations) in addition to the existing devices (that use the host platform's default implementation), however this would mean that any system assuming only one mouse/keyboard device exists would not work with the additional Barrier input devices. We can iterate on both of the above issues in the future as needed, perhaps providing configurable options to control the behaviour.

Signed-off-by: bosnichd <[email protected]>
bosnichd 4 роки тому
батько
коміт
90e45a5bbd

+ 11 - 0
Code/Framework/AzCore/AzCore/std/containers/fixed_unordered_map.h

@@ -120,6 +120,11 @@ namespace AZStd
                 base_type::insert(*first);
             }
         }
+        fixed_unordered_map(const AZStd::initializer_list<value_type>& list, const hasher& hash = hasher(),
+            const key_eq& keyEqual = key_eq())
+            : fixed_unordered_map(list.begin(), list.end(), hash, keyEqual)
+        {
+        }
 
         AZ_FORCE_INLINE pair_iter_bool insert(const value_type& value)
         {
@@ -241,6 +246,12 @@ namespace AZStd
                 base_type::insert(*first);
             }
         }
+        fixed_unordered_multimap(const AZStd::initializer_list<value_type>& list, const hasher& hash = hasher(),
+            const key_eq& keyEqual = key_eq())
+            : fixed_unordered_multimap(list.begin(), list.end(), hash, keyEqual)
+        {
+        }
+
         AZ_FORCE_INLINE iterator insert(const value_type& value)
         {
             return base_type::insert_impl(value).first;

+ 9 - 0
Gems/BarrierInput/CMakeLists.txt

@@ -0,0 +1,9 @@
+#
+# 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
+#
+#
+
+add_subdirectory(Code)

+ 45 - 0
Gems/BarrierInput/Code/CMakeLists.txt

@@ -0,0 +1,45 @@
+#
+# 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
+#
+#
+
+ly_add_target(
+    NAME BarrierInput.Static STATIC
+    NAMESPACE Gem
+    FILES_CMAKE
+        barrierinput_files.cmake
+    INCLUDE_DIRECTORIES
+        PRIVATE
+            Source
+        PUBLIC
+            Include
+    BUILD_DEPENDENCIES
+        PUBLIC
+            AZ::AzCore
+            AZ::AzFramework
+            AZ::AtomCore
+            Gem::Atom_RPI.Public
+    RUNTIME_DEPENDENCIES
+        Gem::Atom_RPI.Private
+)
+
+ly_add_target(
+    NAME BarrierInput ${PAL_TRAIT_MONOLITHIC_DRIVEN_MODULE_TYPE}
+    NAMESPACE Gem
+    FILES_CMAKE
+        barrierinput_shared_files.cmake
+    INCLUDE_DIRECTORIES
+        PRIVATE
+            Source
+        PUBLIC
+            Include
+    BUILD_DEPENDENCIES
+        PRIVATE
+            Gem::BarrierInput.Static
+)
+
+# Barrier Input is only needed for the client:
+ly_create_alias(NAME BarrierInput.Clients  NAMESPACE Gem TARGETS Gem::BarrierInput)

+ 102 - 0
Gems/BarrierInput/Code/Include/BarrierInput/RawInputNotificationBus_Barrier.h

@@ -0,0 +1,102 @@
+/*
+ * 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
+ *
+ */
+
+#pragma once
+
+#include <AzCore/EBus/EBus.h>
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+namespace BarrierInput
+{
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    //! Barrier keyboard modifier bit mask
+    enum ModifierMask
+    {
+        ModifierMask_None       = 0x0000,
+        ModifierMask_Shift      = 0x0001,
+        ModifierMask_Ctrl       = 0x0002,
+        ModifierMask_AltL       = 0x0004,
+        ModifierMask_Windows    = 0x0010,
+        ModifierMask_AltR       = 0x0020,
+        ModifierMask_CapsLock   = 0x1000,
+        ModifierMask_NumLock    = 0x2000,
+        ModifierMask_ScrollLock = 0x4000,
+    };
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    //! EBus interface used to listen for raw Barrier input as broadcast by the BarrierClient.
+    //!
+    //! It's possible to receive multiple events per button/key per frame, and it's very likely that
+    //! Barrier input events will not be dispatched from the main thread, so care should be taken to
+    //! ensure thread safety when implementing event handlers that connect to this Barrier event bus.
+    //!
+    //! This EBus is intended primarily for the BarrierClient to send raw input to Barrier devices.
+    //! Most systems that need to process input should use the generic AzFramework input interfaces,
+    //! but if necessary it is perfectly valid to connect directly to this EBus for Barrier events.
+    class RawInputNotificationsBarrier : public AZ::EBusTraits
+    {
+    public:
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! EBus Trait: raw input notifications are addressed to a single address
+        static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! EBus Trait: raw input notifications can be handled by multiple listeners
+        static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! Default destructor
+        virtual ~RawInputNotificationsBarrier() = default;
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! Process raw mouse button down events (assumed to be dispatched from any thread)
+        //! \param[in] buttonIndex The index of the button that was pressed down
+        virtual void OnRawMouseButtonDownEvent([[maybe_unused]]uint32_t buttonIndex) {}
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! Process raw mouse button up events (assumed to be dispatched from any thread)
+        //! \param[in] buttonIndex The index of the button that was released up
+        virtual void OnRawMouseButtonUpEvent([[maybe_unused]]uint32_t buttonIndex) {}
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! Process raw mouse movement events (assumed to be dispatched from any thread)
+        //! \param[in] movementX The x movement of the mouse
+        //! \param[in] movementY The y movement of the mouse
+        virtual void OnRawMouseMovementEvent([[maybe_unused]]float movementX, [[maybe_unused]]float movementY) {}
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! Process raw mouse position events (assumed to be dispatched from any thread)
+        //! \param[in] positionX The x position of the mouse
+        //! \param[in] positionY The y position of the mouse
+        virtual void OnRawMousePositionEvent([[maybe_unused]]float positionX, [[maybe_unused]]float positionY) {}
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! Process raw keyboard key down events (assumed to be dispatched from any thread)
+        //! \param[in] scanCode The scan code of the key that was pressed down
+        //! \param[in] activeModifiers The bit mask of currently active modifier keys
+        virtual void OnRawKeyboardKeyDownEvent([[maybe_unused]]uint32_t scanCode, [[maybe_unused]]ModifierMask activeModifiers) {}
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! Process raw keyboard key up events (assumed to be dispatched from any thread)
+        //! \param[in] scanCode The scan code of the key that was released up
+        //! \param[in] activeModifiers The bit mask of currently active modifier keys
+        virtual void OnRawKeyboardKeyUpEvent([[maybe_unused]]uint32_t scanCode, [[maybe_unused]]ModifierMask activeModifiers) {}
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! Process raw keyboard key repeat events (assumed to be dispatched from any thread)
+        //! \param[in] scanCode The scan code of the key that was repeatedly held down
+        //! \param[in] activeModifiers The bit mask of currently active modifier keys
+        virtual void OnRawKeyboardKeyRepeatEvent([[maybe_unused]]uint32_t scanCode, [[maybe_unused]]ModifierMask activeModifiers) {}
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! Process raw clipboard events (assumed to be dispatched from any thread)
+        //! \param[in] clipboardContents The contents of the clipboard
+        virtual void OnRawClipboardEvent([[maybe_unused]]const char* clipboardContents) {}
+    };
+    using RawInputNotificationBusBarrier = AZ::EBus<RawInputNotificationsBarrier>;
+} // namespace BarrierInput

+ 398 - 0
Gems/BarrierInput/Code/Source/BarrierInputClient.cpp

@@ -0,0 +1,398 @@
+/*
+ * 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 <BarrierInputClient.h>
+#include <BarrierInput/RawInputNotificationBus_Barrier.h>
+
+#include <Atom/RPI.Public/ViewportContext.h>
+#include <Atom/RPI.Public/ViewportContextBus.h>
+
+#include <AzCore/Console/ILogger.h>
+#include <AzCore/Socket/AzSocket.h>
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// The majority of this file was resurrected from legacy code, and could use some love, but it works.
+namespace BarrierInput
+{
+    struct Stream
+    {
+        explicit Stream(int size)
+        {
+            buffer = (AZ::u8*)malloc(size);
+            end = data = buffer;
+            bufferSize = size;
+            packet = nullptr;
+        }
+
+        ~Stream()
+        {
+            free(buffer);
+        }
+
+        AZ::u8* data;
+        AZ::u8* end;
+        AZ::u8* buffer;
+        AZ::u8* packet;
+        int bufferSize;
+
+        void Rewind() { data = buffer; }
+        int GetBufferSize() { return bufferSize; }
+
+        char* GetBuffer() { return (char*)buffer; }
+        char* GetData() { return (char*)data; }
+
+        void SetLength(int len) { end = data + len; }
+        int GetLength() { return (int)(end - data); }
+
+        int ReadU32() { int ret = (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3]; data += 4; return ret; }
+        int ReadU16() { int ret = (data[0] << 8) | data[1]; data += 2; return ret; }
+        int ReadU8() { int ret = data[0]; data += 1; return ret; }
+        void Eat(int len) { data += len; }
+
+        void InsertString(const char* str) { int len = strlen(str); memcpy(end, str, len); end += len; }
+        void InsertU32(int a) { end[0] = a >> 24; end[1] = a >> 16; end[2] = a >> 8; end[3] = a; end += 4; }
+        void InsertU16(int a) { end[0] = a >> 8; end[1] = a; end += 2; }
+        void InsertU8(int a) { end[0] = a; end += 1; }
+        void OpenPacket() { packet = end; end += 4; }
+        void ClosePacket() { int len = GetLength() - sizeof(AZ::u32); packet[0] = len >> 24; packet[1] = len >> 16; packet[2] = len >> 8; packet[3] = len; packet = NULL; }
+    };
+
+    enum ArgType
+    {
+        ARG_END = 0,
+        ARG_UINT8,
+        ARG_UINT16,
+        ARG_UINT32
+    };
+    constexpr int MAX_ARGS = 16;
+
+    typedef bool (*packetCallback)(BarrierClient* pContext, int* pArgs, Stream* pStream, int streamLeft);
+
+    struct Packet
+    {
+        const char* pattern;
+        ArgType args[MAX_ARGS + 1];
+        packetCallback callback;
+    };
+
+    static bool barrierSendFunc(BarrierClient* pContext, const char* buffer, int length)
+    {
+        int ret = AZ::AzSock::Send(pContext->GetSocket(), buffer, length, 0);
+        return (ret == length) ? true : false;
+    }
+
+    static bool barrierPacket(BarrierClient* pContext, [[maybe_unused]]int* pArgs, [[maybe_unused]]Stream* pStream, [[maybe_unused]]int streamLeft)
+    {
+        Stream stream(256);
+        stream.OpenPacket();
+        stream.InsertString("Barrier");
+        stream.InsertU16(1);
+        stream.InsertU16(4);
+        stream.InsertU32(pContext->GetClientScreenName().length());
+        stream.InsertString(pContext->GetClientScreenName().c_str());
+        stream.ClosePacket();
+        return barrierSendFunc(pContext, stream.GetBuffer(), stream.GetLength());
+    }
+
+    static bool barrierQueryInfo(BarrierClient* pContext, [[maybe_unused]]int* pArgs, [[maybe_unused]]Stream* pStream, [[maybe_unused]]int streamLeft)
+    {
+        Stream stream(256);
+        stream.OpenPacket();
+        stream.InsertString("DINF");
+        stream.InsertU16(0);
+        stream.InsertU16(0);
+
+        auto atomViewportRequests = AZ::Interface<AZ::RPI::ViewportContextRequestsInterface>::Get();
+        AZ::RPI::ViewportContextPtr viewportContext = atomViewportRequests->GetDefaultViewportContext();
+        if (viewportContext)
+        {
+            const AzFramework::WindowSize windowSize = viewportContext->GetViewportSize();
+            stream.InsertU16(windowSize.m_width);
+            stream.InsertU16(windowSize.m_height);
+        }
+        else
+        {
+            stream.InsertU16(1920);
+            stream.InsertU16(1080);
+        }
+        stream.InsertU16(0);
+        stream.InsertU16(0);
+        stream.InsertU16(0);
+        stream.ClosePacket();
+        return barrierSendFunc(pContext, stream.GetBuffer(), stream.GetLength());
+    }
+
+    static bool barrierKeepAlive(BarrierClient* pContext, [[maybe_unused]]int* pArgs, [[maybe_unused]]Stream* pStream, [[maybe_unused]]int streamLeft)
+    {
+        Stream stream(256);
+        stream.OpenPacket();
+        stream.InsertString("CALV");
+        stream.ClosePacket();
+        return barrierSendFunc(pContext, stream.GetBuffer(), stream.GetLength());
+    }
+
+    static bool barrierEnterScreen([[maybe_unused]]BarrierClient* pContext, int* pArgs, [[maybe_unused]]Stream* pStream, [[maybe_unused]]int streamLeft)
+    {
+        const float positionX = static_cast<float>(pArgs[0]);
+        const float positionY = static_cast<float>(pArgs[1]);
+        RawInputNotificationBusBarrier::Broadcast(&RawInputNotificationsBarrier::OnRawMousePositionEvent,
+                                                  positionX,
+                                                  positionY);
+        return true;
+    }
+
+    static bool barrierExitScreen([[maybe_unused]]BarrierClient* pContext, [[maybe_unused]]int* pArgs, [[maybe_unused]]Stream* pStream, [[maybe_unused]]int streamLeft)
+    {
+        return true;
+    }
+
+    static bool barrierMouseMove([[maybe_unused]]BarrierClient* pContext, int* pArgs, [[maybe_unused]]Stream* pStream, [[maybe_unused]]int streamLeft)
+    {
+        const float positionX = static_cast<float>(pArgs[0]);
+        const float positionY = static_cast<float>(pArgs[1]);
+        RawInputNotificationBusBarrier::Broadcast(&RawInputNotificationsBarrier::OnRawMousePositionEvent,
+                                                  positionX,
+                                                  positionY);
+        return true;
+    }
+
+    static bool barrierMouseMoveRelative([[maybe_unused]]BarrierClient* pContext, int* pArgs, [[maybe_unused]]Stream* pStream, [[maybe_unused]]int streamLeft)
+    {
+        const float movementX = static_cast<float>(pArgs[0]);
+        const float movementY = static_cast<float>(pArgs[1]);
+        RawInputNotificationBusBarrier::Broadcast(&RawInputNotificationsBarrier::OnRawMouseMovementEvent,
+                                                  movementX,
+                                                  movementY);
+        return true;
+    }
+
+    static bool barrierMouseButtonDown([[maybe_unused]]BarrierClient* pContext, int* pArgs, [[maybe_unused]]Stream* pStream, [[maybe_unused]]int streamLeft)
+    {
+        const uint32_t buttonIndex = pArgs[0];
+        RawInputNotificationBusBarrier::Broadcast(&RawInputNotificationsBarrier::OnRawMouseButtonDownEvent, buttonIndex);
+        return true;
+    }
+
+    static bool barrierMouseButtonUp([[maybe_unused]]BarrierClient* pContext, int* pArgs, [[maybe_unused]]Stream* pStream, [[maybe_unused]]int streamLeft)
+    {
+        const uint32_t buttonIndex = pArgs[0];
+        RawInputNotificationBusBarrier::Broadcast(&RawInputNotificationsBarrier::OnRawMouseButtonUpEvent, buttonIndex);
+        return true;
+    }
+
+    static bool barrierKeyboardDown([[maybe_unused]]BarrierClient* pContext, int* pArgs, [[maybe_unused]]Stream* pStream, [[maybe_unused]]int streamLeft)
+    {
+        const uint32_t scanCode = pArgs[2];
+        const ModifierMask activeModifiers = static_cast<ModifierMask>(pArgs[1]);
+        RawInputNotificationBusBarrier::Broadcast(&RawInputNotificationsBarrier::OnRawKeyboardKeyDownEvent, scanCode, activeModifiers);
+        return true;
+    }
+
+    static bool barrierKeyboardUp([[maybe_unused]]BarrierClient* pContext, int* pArgs, [[maybe_unused]]Stream* pStream, [[maybe_unused]]int streamLeft)
+    {
+        const uint32_t scanCode = pArgs[2];
+        const ModifierMask activeModifiers = static_cast<ModifierMask>(pArgs[1]);
+        RawInputNotificationBusBarrier::Broadcast(&RawInputNotificationsBarrier::OnRawKeyboardKeyUpEvent, scanCode, activeModifiers);
+        return true;
+    }
+
+    static bool barrierKeyboardRepeat([[maybe_unused]]BarrierClient* pContext, int* pArgs, [[maybe_unused]]Stream* pStream, [[maybe_unused]]int streamLeft)
+    {
+        const uint32_t scanCode = pArgs[2];
+        const ModifierMask activeModifiers = static_cast<ModifierMask>(pArgs[1]);
+        RawInputNotificationBusBarrier::Broadcast(&RawInputNotificationsBarrier::OnRawKeyboardKeyRepeatEvent, scanCode, activeModifiers);
+        return true;
+    }
+
+    static bool barrierClipboard([[maybe_unused]]BarrierClient* pContext, int* pArgs, Stream* pStream, [[maybe_unused]]int streamLeft)
+    {
+        for (int i = 0; i < pArgs[3]; i++)
+        {
+            int format = pStream->ReadU32();
+            int size = pStream->ReadU32();
+            if (format == 0) // Is text
+            {
+                char* clipboardContents = new char[size];
+                memcpy(clipboardContents, pStream->GetData(), size);
+                clipboardContents[size] = '\0';
+                RawInputNotificationBusBarrier::Broadcast(&RawInputNotificationsBarrier::OnRawClipboardEvent, clipboardContents);
+                delete[] clipboardContents;
+            }
+            pStream->Eat(size);
+        }
+        return true;
+    }
+
+    static bool barrierBye([[maybe_unused]]BarrierClient* pContext, [[maybe_unused]]int* pArgs, [[maybe_unused]]Stream* pStream, [[maybe_unused]]int streamLeft)
+    {
+        AZLOG_INFO("BarrierClient: Server said bye. Disconnecting\n");
+        return false;
+    }
+
+    static Packet s_packets[] = {
+        { "Barrier", { ARG_UINT16, ARG_UINT16 }, barrierPacket },
+        { "QINF", {}, barrierQueryInfo },
+        { "CALV", {}, barrierKeepAlive },
+        { "CINN", { ARG_UINT16, ARG_UINT16, ARG_UINT32, ARG_UINT16 }, barrierEnterScreen },
+        { "COUT", { }, barrierExitScreen },
+        { "CBYE", { }, barrierBye },
+        { "DMMV", { ARG_UINT16, ARG_UINT16 }, barrierMouseMove },
+        { "DMRM", { ARG_UINT16, ARG_UINT16 }, barrierMouseMoveRelative },
+        { "DMDN", { ARG_UINT8 }, barrierMouseButtonDown },
+        { "DMUP", { ARG_UINT8 }, barrierMouseButtonUp },
+        { "DKDN", { ARG_UINT16, ARG_UINT16, ARG_UINT16 }, barrierKeyboardDown },
+        { "DKUP", { ARG_UINT16, ARG_UINT16, ARG_UINT16 }, barrierKeyboardUp },
+        { "DKRP", { ARG_UINT16, ARG_UINT16, ARG_UINT16, ARG_UINT16 }, barrierKeyboardRepeat },
+        { "DCLP", { ARG_UINT8, ARG_UINT32, ARG_UINT32, ARG_UINT32 }, barrierClipboard }
+    };
+
+    static bool ProcessPackets(BarrierClient* pContext, Stream& stream)
+    {
+        while (stream.data < stream.end)
+        {
+            const int packetLength = stream.ReadU32();
+            const int streamLength = stream.GetLength();
+            const char* packetStart = stream.GetData();
+            if (packetLength > streamLength)
+            {
+                AZLOG_INFO("BarrierClient: Packet overruns buffer (Packet Length: %d Buffer Length: %d), probably lots of data on clipboard?\n", packetLength, streamLength);
+                return false;
+            }
+
+            const int numPackets = sizeof(s_packets) / sizeof(s_packets[0]);
+            int i;
+            for (i = 0; i < numPackets; ++i)
+            {
+                const int len = strlen(s_packets[i].pattern);
+                if (packetLength >= len && memcmp(stream.GetData(), s_packets[i].pattern, len) == 0)
+                {
+                    bool bDone = false;
+                    int numArgs = 0;
+                    int args[MAX_ARGS];
+                    stream.Eat(len);
+                    while (!bDone)
+                    {
+                        switch (s_packets[i].args[numArgs])
+                        {
+                        case ARG_UINT8:
+                            args[numArgs++] = stream.ReadU8();
+                            break;
+                        case ARG_UINT16:
+                            args[numArgs++] = stream.ReadU16();
+                            break;
+                        case ARG_UINT32:
+                            args[numArgs++] = stream.ReadU32();
+                            break;
+                        case ARG_END:
+                            bDone = true;
+                            break;
+                        }
+                    }
+                    if (s_packets[i].callback)
+                    {
+                        if (!s_packets[i].callback(pContext, args, &stream, packetLength - (int)(stream.GetData() - packetStart)))
+                        {
+                            return false;
+                        }
+                    }
+                    stream.Eat(packetLength - (int)(stream.GetData() - packetStart));
+                    break;
+                }
+            }
+            if (i == numPackets)
+            {
+                stream.Eat(packetLength);
+            }
+        }
+        return true;
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    BarrierClient::BarrierClient(const char* clientScreenName, const char* serverHostName, AZ::u32 connectionPort)
+        : m_clientScreenName(clientScreenName)
+        , m_serverHostName(serverHostName)
+        , m_connectionPort(connectionPort)
+        , m_socket(AZ_SOCKET_INVALID)
+        , m_threadHandle()
+        , m_threadQuit(false)
+    {
+        AZStd::thread_desc threadDesc;
+        threadDesc.m_name = "BarrierInputClientThread";
+        m_threadHandle = AZStd::thread(AZStd::bind(&BarrierClient::Run, this), &threadDesc);
+    }
+    
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    BarrierClient::~BarrierClient()
+    {
+        if (AZ::AzSock::IsAzSocketValid(m_socket))
+        {
+            AZ::AzSock::CloseSocket(m_socket);
+        }
+        m_threadQuit = true;
+        m_threadHandle.join();
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    void BarrierClient::Run()
+    {
+        Stream stream(4 * 1024);
+        bool connected = false;
+        while (!m_threadQuit)
+        {
+            if (!connected)
+            {
+                connected = ConnectToServer();
+                continue;
+            }
+
+            const int lengthReceived = AZ::AzSock::Recv(m_socket, stream.GetBuffer(), stream.GetBufferSize(), 0);
+            if (lengthReceived <= 0)
+            {
+                AZLOG_INFO("BarrierClient: Receive failed, reconnecting.\n");
+                connected = false;
+                continue;
+            }
+
+            stream.Rewind();
+            stream.SetLength(lengthReceived);
+            if (!ProcessPackets(this, stream))
+            {
+                AZLOG_INFO("BarrierClient: Packet processing failed, reconnecting.\n");
+                connected = false;
+                continue;
+            }
+        }
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    bool BarrierClient::ConnectToServer()
+    {
+        if (AZ::AzSock::IsAzSocketValid(m_socket))
+        {
+            AZ::AzSock::CloseSocket(m_socket);
+        }
+
+        m_socket = AZ::AzSock::Socket();
+        if (AZ::AzSock::IsAzSocketValid(m_socket))
+        {
+            AZ::AzSock::AzSocketAddress socketAddress;
+            if (socketAddress.SetAddress(m_serverHostName.c_str(), m_connectionPort))
+            {
+                const int result = AZ::AzSock::Connect(m_socket, socketAddress);
+                if (!AZ::AzSock::SocketErrorOccured(result))
+                {
+                    return true;
+                }
+            }
+            AZ::AzSock::CloseSocket(m_socket);
+            m_socket = AZ_SOCKET_INVALID;
+        }
+
+        return false;
+    }
+} // namespace BarrierInput

+ 80 - 0
Gems/BarrierInput/Code/Source/BarrierInputClient.h

@@ -0,0 +1,80 @@
+/*
+ * 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
+ *
+ */
+
+#pragma once
+
+#include <AzCore/Memory/SystemAllocator.h>
+#include <AzCore/Socket/AzSocket_fwd.h>
+#include <AzCore/std/parallel/thread.h>
+#include <AzCore/std/parallel/atomic.h>
+#include <AzCore/std/string/string.h>
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+namespace BarrierInput
+{
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    //! Barrier client that manages a connection with a Barrier server.
+    class BarrierClient
+    {
+    public:
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        static constexpr AZ::u32 DEFAULT_BARRIER_CONNECTION_PORT_NUMBER = 24800;
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        // Allocator
+        AZ_CLASS_ALLOCATOR(BarrierClient, AZ::SystemAllocator, 0);
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! Constructor
+        //! \param[in] clientScreenName Name of the Barrier client screen this class implements
+        //! \param[in] serverHostName Name of the Barrier server host this client connects to
+        //! \param[in] connectionPort Port number over which to connect to the Barrier server
+        BarrierClient(const char* clientScreenName,
+                      const char* serverHostName,
+                      AZ::u32 connectionPort = DEFAULT_BARRIER_CONNECTION_PORT_NUMBER);
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! Destructor
+        ~BarrierClient();
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! Access to the Barrier client screen this class implements
+        //! \return Name of the Barrier client screen this class implements
+        const AZStd::string& GetClientScreenName() const { return m_clientScreenName; }
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! Access to the Barrier server host this client connects to
+        //! \return Name of the Barrier server host this client connects to
+        const AZStd::string& GetServerHostName() const { return m_serverHostName; }
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! Access to the socket the Barrier client is communicating over
+        //! \return The socket the Barrier client is communicating over
+        const AZSOCKET& GetSocket() const { return m_socket; }
+
+    protected:
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! The client connection loop that runs in it's own thread
+        void Run();
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! Try to connect to the Barrier server
+        //! \return True if we're connected to the Barrier server, false otherwise
+        bool ConnectToServer();
+
+    private:
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        // Variables
+        AZStd::string       m_clientScreenName;
+        AZStd::string       m_serverHostName;
+        AZ::u32             m_connectionPort;
+        AZStd::thread       m_threadHandle;
+        AZStd::atomic_bool  m_threadQuit;
+        AZSOCKET            m_socket;
+    };
+} // namespace BarrierInput

+ 260 - 0
Gems/BarrierInput/Code/Source/BarrierInputKeyboard.cpp

@@ -0,0 +1,260 @@
+/*
+ * 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 <BarrierInputKeyboard.h>
+
+#include <AzFramework/Input/Devices/Keyboard/InputDeviceKeyboardWindowsScanCodes.h>
+
+#include <AzCore/std/containers/fixed_unordered_map.h>
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+namespace BarrierInput
+{
+    using namespace AzFramework;
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    InputDeviceKeyboard::Implementation* InputDeviceKeyboardBarrier::Create(InputDeviceKeyboard& inputDevice)
+    {
+        return aznew InputDeviceKeyboardBarrier(inputDevice);
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    InputDeviceKeyboardBarrier::InputDeviceKeyboardBarrier(InputDeviceKeyboard& inputDevice)
+        : InputDeviceKeyboard::Implementation(inputDevice)
+        , m_threadAwareRawKeyEventQueuesById()
+        , m_threadAwareRawKeyEventQueuesByIdMutex()
+        , m_threadAwareRawTextEventQueue()
+        , m_threadAwareRawTextEventQueueMutex()
+        , m_hasTextEntryStarted(false)
+    {
+        RawInputNotificationBusBarrier::Handler::BusConnect();
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    InputDeviceKeyboardBarrier::~InputDeviceKeyboardBarrier()
+    {
+        RawInputNotificationBusBarrier::Handler::BusDisconnect();
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    bool InputDeviceKeyboardBarrier::IsConnected() const
+    {
+        // We could check the validity of the socket connection to the Barrier server
+        return true;
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    bool InputDeviceKeyboardBarrier::HasTextEntryStarted() const
+    {
+        return m_hasTextEntryStarted;
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    void InputDeviceKeyboardBarrier::TextEntryStart(const InputTextEntryRequests::VirtualKeyboardOptions&)
+    {
+        m_hasTextEntryStarted = true;
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    void InputDeviceKeyboardBarrier::TextEntryStop()
+    {
+        m_hasTextEntryStarted = false;
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    void InputDeviceKeyboardBarrier::TickInputDevice()
+    {
+        {
+            // Queue all key events that were received in the other thread
+            AZStd::scoped_lock lock(m_threadAwareRawKeyEventQueuesByIdMutex);
+            for (const auto& keyEventQueuesById : m_threadAwareRawKeyEventQueuesById)
+            {
+                const InputChannelId& inputChannelId = keyEventQueuesById.first;
+                for (bool rawKeyState : keyEventQueuesById.second)
+                {
+                    QueueRawKeyEvent(inputChannelId, rawKeyState);
+                }
+            }
+            m_threadAwareRawKeyEventQueuesById.clear();
+        }
+
+        {
+            // Queue all text events that were received in the other thread
+            AZStd::scoped_lock lock(m_threadAwareRawTextEventQueueMutex);
+            for (const AZStd::string& rawTextEvent : m_threadAwareRawTextEventQueue)
+            {
+            #if !defined(ALWAYS_DISPATCH_KEYBOARD_TEXT_INPUT)
+                if (!m_hasTextEntryStarted)
+                {
+                    continue;
+                }
+            #endif // !defined(ALWAYS_DISPATCH_KEYBOARD_TEXT_INPUT)
+                QueueRawTextEvent(rawTextEvent);
+            }
+            m_threadAwareRawTextEventQueue.clear();
+        }
+
+        // Process raw event queues once each frame
+        ProcessRawEventQueues();
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    void InputDeviceKeyboardBarrier::OnRawKeyboardKeyDownEvent(uint32_t scanCode,
+                                                               ModifierMask activeModifiers)
+    {
+        // Queue key events and text events
+        ThreadSafeQueueRawKeyEvent(scanCode, true);
+        if (char asciiChar = TranslateRawKeyEventToASCIIChar(scanCode, activeModifiers))
+        {
+            const AZStd::string text(1, asciiChar);
+            ThreadSafeQueueRawTextEvent(text);
+        }
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    void InputDeviceKeyboardBarrier::OnRawKeyboardKeyUpEvent(uint32_t scanCode,
+                                                             [[maybe_unused]]ModifierMask activeModifiers)
+    {
+        // Queue key events, not text events
+        ThreadSafeQueueRawKeyEvent(scanCode, false);
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    void InputDeviceKeyboardBarrier::OnRawKeyboardKeyRepeatEvent(uint32_t scanCode,
+                                                                 ModifierMask activeModifiers)
+    {
+        // Don't queue key events, only text events
+        if (char asciiChar = TranslateRawKeyEventToASCIIChar(scanCode, activeModifiers))
+        {
+            const AZStd::string text(1, asciiChar);
+            ThreadSafeQueueRawTextEvent(text);
+        }
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    void InputDeviceKeyboardBarrier::ThreadSafeQueueRawKeyEvent(uint32_t scanCode, bool rawKeyState)
+    {
+        // From observation, Barrier scan codes in the:
+        // - Range 0x0-0x7F (0-127) correspond to windows scan codes without the extended bit set
+        // - Range 0x100-0x17F (256-383) correspond to windows scan codes with the extended bit set
+        const InputChannelId* inputChannelId = nullptr;
+        if (scanCode < InputChannelIdByScanCodeTable.size())
+        {
+            inputChannelId = InputChannelIdByScanCodeTable[scanCode];
+        }
+        else if (0 <= (scanCode - 0x100) && scanCode < InputChannelIdByScanCodeWithExtendedPrefixTable.size())
+        {
+            inputChannelId = InputChannelIdByScanCodeWithExtendedPrefixTable[scanCode - 0x100];
+        }
+
+        if (inputChannelId)
+        {
+            AZStd::scoped_lock lock(m_threadAwareRawKeyEventQueuesByIdMutex);
+            m_threadAwareRawKeyEventQueuesById[*inputChannelId].push_back(rawKeyState);
+        }
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    void InputDeviceKeyboardBarrier::ThreadSafeQueueRawTextEvent(const AZStd::string& textUTF8)
+    {
+        AZStd::scoped_lock lock(m_threadAwareRawTextEventQueueMutex);
+        m_threadAwareRawTextEventQueue.push_back(textUTF8);
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    char InputDeviceKeyboardBarrier::TranslateRawKeyEventToASCIIChar(uint32_t scanCode,
+                                                                     ModifierMask activeModifiers)
+    {
+        // Map ASCII character pairs keyed by their keyboard scan code, assuming an ANSI mechanical
+        // keyboard layout with a standard QWERTY key mapping. The first element of the pair is the
+        // character that should be produced if the key is pressed while no shift or caps modifiers
+        // are active, while the second element is the character that should be produced if the key
+        // is pressed while a shift or caps modifier is active. Required because Barrier only sends
+        // raw key events, not translated text input. While we would ideally support the full range
+        // of UTF-8 text input, that is beyond the scope of this debug/development only class. Note
+        // that this function assumes an ANSI mechanical keyboard layout with a standard QWERTY key
+        // mapping, and will not produce correct results if used with other key layouts or mappings.
+        static const AZStd::fixed_unordered_map<AZ::u32, AZStd::pair<char, char>, 16, 64> ScanCodeToASCIICharMap =
+        {
+            {   2, { '1', '!' } },
+            {   3, { '2', '@' } },
+            {   4, { '3', '#' } },
+            {   5, { '4', '$' } },
+            {   6, { '5', '%' } },
+            {   7, { '6', '^' } },
+            {   8, { '7', '&' } },
+            {   9, { '8', '*' } },
+            {  10, { '9', '(' } },
+            {  11, { '0', ')' } },
+            {  12, { '-', '_' } },
+            {  13, { '=', '+' } },
+            {  15, { '\t', '\t' } },
+            {  16, { 'q', 'Q' } },
+            {  17, { 'w', 'W' } },
+            {  18, { 'e', 'E' } },
+            {  19, { 'r', 'R' } },
+            {  20, { 't', 'T' } },
+            {  21, { 'y', 'Y' } },
+            {  22, { 'u', 'U' } },
+            {  23, { 'i', 'I' } },
+            {  24, { 'o', 'O' } },
+            {  25, { 'p', 'P' } },
+            {  26, { '[', '{' } },
+            {  27, { ']', '}' } },
+            {  30, { 'a', 'A' } },
+            {  31, { 's', 'S' } },
+            {  32, { 'd', 'D' } },
+            {  33, { 'f', 'F' } },
+            {  34, { 'g', 'G' } },
+            {  35, { 'h', 'H' } },
+            {  36, { 'j', 'J' } },
+            {  37, { 'k', 'K' } },
+            {  38, { 'l', 'L' } },
+            {  39, { ';', ':' } },
+            {  40, { '\'', '"' } },
+            {  41, { '`', '~' } },
+            {  43, { '\\', '|' } },
+            {  44, { 'z', 'Z' } },
+            {  45, { 'x', 'X' } },
+            {  46, { 'c', 'C' } },
+            {  47, { 'v', 'V' } },
+            {  48, { 'b', 'B' } },
+            {  49, { 'n', 'N' } },
+            {  50, { 'm', 'M' } },
+            {  51, { ',', '<' } },
+            {  52, { '.', '>' } },
+            {  53, { '/', '?' } },
+            {  55, { '*', '*' } },
+            {  57, { ' ', ' ' } },
+            {  71, { '7', '7' } },
+            {  72, { '8', '8' } },
+            {  73, { '9', '9' } },
+            {  74, { '-', '-' } },
+            {  75, { '4', '4' } },
+            {  76, { '5', '5' } },
+            {  77, { '6', '6' } },
+            {  78, { '+', '+' } },
+            {  79, { '1', '1' } },
+            {  80, { '2', '2' } },
+            {  81, { '3', '3' } },
+            {  82, { '0', '0' } },
+            {  83, { '.', '.' } },
+            { 309, { '/', '/' } }
+        };
+
+        const auto& it = ScanCodeToASCIICharMap.find(scanCode);
+        if (it == ScanCodeToASCIICharMap.end())
+        {
+            return '\0';
+        }
+
+        const bool shiftOrCapsLockActive = (activeModifiers & ModifierMask_Shift) ||
+                                           (activeModifiers & ModifierMask_CapsLock);
+        return shiftOrCapsLockActive ? it->second.second : it->second.first;
+    }
+} // namespace BarrierInput

+ 109 - 0
Gems/BarrierInput/Code/Source/BarrierInputKeyboard.h

@@ -0,0 +1,109 @@
+/*
+ * 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
+ *
+ */
+
+#pragma once
+
+#include <BarrierInput/RawInputNotificationBus_Barrier.h>
+
+#include <AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard.h>
+
+#include <AzCore/std/parallel/mutex.h>
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+namespace BarrierInput
+{
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    //! Barrier specific implementation for keyboard input devices.
+    class InputDeviceKeyboardBarrier : public AzFramework::InputDeviceKeyboard::Implementation
+                                     , public RawInputNotificationBusBarrier::Handler
+    {
+    public:
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        // Allocator
+        AZ_CLASS_ALLOCATOR(InputDeviceKeyboardBarrier, AZ::SystemAllocator, 0);
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! Custom factory create function
+        //! \param[in] inputDevice Reference to the input device being implemented
+        static Implementation* Create(AzFramework::InputDeviceKeyboard& inputDevice);
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! Constructor
+        //! \param[in] inputDevice Reference to the input device being implemented
+        InputDeviceKeyboardBarrier(AzFramework::InputDeviceKeyboard& inputDevice);
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! Destructor
+        ~InputDeviceKeyboardBarrier() override;
+
+    private:
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! \ref AzFramework::InputDeviceKeyboard::Implementation::IsConnected
+        bool IsConnected() const override;
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! \ref AzFramework::InputDeviceKeyboard::Implementation::HasTextEntryStarted
+        bool HasTextEntryStarted() const override;
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! \ref AzFramework::InputDeviceKeyboard::Implementation::TextEntryStart
+        void TextEntryStart(const AzFramework::InputTextEntryRequests::VirtualKeyboardOptions& options) override;
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! \ref AzFramework::InputDeviceKeyboard::Implementation::TextEntryStop
+        void TextEntryStop() override;
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! \ref AzFramework::InputDeviceKeyboard::Implementation::TickInputDevice
+        void TickInputDevice() override;
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! \ref RawInputNotificationsBarrier::OnRawKeyboardKeyDownEvent
+        virtual void OnRawKeyboardKeyDownEvent(uint32_t scanCode, ModifierMask activeModifiers);
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! \ref RawInputNotificationsBarrier::OnRawKeyboardKeyUpEvent
+        virtual void OnRawKeyboardKeyUpEvent(uint32_t scanCode, ModifierMask activeModifiers);
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! \ref RawInputNotificationsBarrier::OnRawKeyboardKeyRepeatEvent
+        virtual void OnRawKeyboardKeyRepeatEvent(uint32_t scanCode, ModifierMask activeModifiers);
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! Thread safe method to queue raw key events to be processed in the main thread update
+        //! \param[in] scanCode The scan code of the key
+        //! \param[in] rawKeyState The raw key state
+        void ThreadSafeQueueRawKeyEvent(uint32_t scanCode, bool rawKeyState);
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! Thread safe method to queue raw text events to be processed in the main thread update
+        //! \param[in] textUTF8 The text to queue (encoded using UTF-8)
+        void ThreadSafeQueueRawTextEvent(const AZStd::string& textUTF8);
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! Translate a key event to an ASCII character. This is required because Barrier only sends
+        //! raw key events, not translated text input. While we would ideally support the full range
+        //! of UTF-8 text input, that is beyond the scope of this debug/development only class. Note
+        //! that this function assumes an ANSI mechanical keyboard layout with a standard QWERTY key
+        //! mapping, and will not produce correct results if used with other key layouts or mappings.
+        //! \param[in] scanCode The scan code of the key
+        //! \param[in] activeModifiers The bit mask of currently active modifier keys
+        //! \return If the scan code and active modifiers produce a valid ASCII character
+        char TranslateRawKeyEventToASCIIChar(uint32_t scanCode, ModifierMask activeModifiers);
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        // Variables
+        RawKeyEventQueueByIdMap      m_threadAwareRawKeyEventQueuesById;
+        AZStd::mutex                 m_threadAwareRawKeyEventQueuesByIdMutex;
+
+        AZStd::vector<AZStd::string> m_threadAwareRawTextEventQueue;
+        AZStd::mutex                 m_threadAwareRawTextEventQueueMutex;
+
+        bool                         m_hasTextEntryStarted;
+    };
+} // namespace BarrierInput

+ 47 - 0
Gems/BarrierInput/Code/Source/BarrierInputModule.cpp

@@ -0,0 +1,47 @@
+/*
+ * 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/Memory/SystemAllocator.h>
+#include <AzCore/Module/Module.h>
+
+#include <BarrierInputSystemComponent.h>
+
+namespace BarrierInput
+{
+    class BarrierInputModule
+        : public AZ::Module
+    {
+    public:
+        AZ_RTTI(BarrierInputModule, "{C338BB3B-EA09-4FC8-AD49-840F8A22837F}", AZ::Module);
+        AZ_CLASS_ALLOCATOR(BarrierInputModule, AZ::SystemAllocator, 0);
+
+        BarrierInputModule()
+            : AZ::Module()
+        {
+            // Push results of [MyComponent]::CreateDescriptor() into m_descriptors here.
+            m_descriptors.insert(m_descriptors.end(), {
+                BarrierInputSystemComponent::CreateDescriptor(),
+            });
+        }
+
+        /**
+         * Add required SystemComponents to the SystemEntity.
+         */
+        AZ::ComponentTypeList GetRequiredSystemComponents() const override
+        {
+            return AZ::ComponentTypeList{
+                azrtti_typeid<BarrierInputSystemComponent>(),
+            };
+        }
+    };
+}
+
+// DO NOT MODIFY THIS LINE UNLESS YOU RENAME THE GEM
+// The first parameter should be GemName_GemIdLower
+// The second should be the fully qualified name of the class above
+AZ_DECLARE_MODULE_CLASS(Gem_BarrierInput, BarrierInput::BarrierInputModule)

+ 195 - 0
Gems/BarrierInput/Code/Source/BarrierInputMouse.cpp

@@ -0,0 +1,195 @@
+/*
+ * 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 <BarrierInputMouse.h>
+
+#include <Atom/RPI.Public/ViewportContext.h>
+#include <Atom/RPI.Public/ViewportContextBus.h>
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+namespace BarrierInput
+{
+    using namespace AzFramework;
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    InputDeviceMouse::Implementation* InputDeviceMouseBarrier::Create(InputDeviceMouse& inputDevice)
+    {
+        return aznew InputDeviceMouseBarrier(inputDevice);
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    InputDeviceMouseBarrier::InputDeviceMouseBarrier(InputDeviceMouse& inputDevice)
+        : InputDeviceMouse::Implementation(inputDevice)
+        , m_systemCursorState(SystemCursorState::Unknown)
+        , m_systemCursorPositionNormalized(0.5f, 0.5f)
+        , m_threadAwareRawButtonEventQueuesById()
+        , m_threadAwareRawButtonEventQueuesByIdMutex()
+        , m_threadAwareRawMovementEventQueuesById()
+        , m_threadAwareRawMovementEventQueuesByIdMutex()
+        , m_threadAwareSystemCursorPosition(0.0f, 0.0f)
+        , m_threadAwareSystemCursorPositionMutex()
+    {
+        RawInputNotificationBusBarrier::Handler::BusConnect();
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    InputDeviceMouseBarrier::~InputDeviceMouseBarrier()
+    {
+        RawInputNotificationBusBarrier::Handler::BusDisconnect();
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    bool InputDeviceMouseBarrier::IsConnected() const
+    {
+        // We could check the validity of the socket connection to the Barrier server
+        return true;
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    void InputDeviceMouseBarrier::SetSystemCursorState(SystemCursorState systemCursorState)
+    {
+        // This doesn't apply when using Barrier, but we'll store it so it can be queried
+        m_systemCursorState = systemCursorState;
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    SystemCursorState InputDeviceMouseBarrier::GetSystemCursorState() const
+    {
+        return m_systemCursorState;
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    void InputDeviceMouseBarrier::SetSystemCursorPositionNormalized(AZ::Vector2 positionNormalized)
+    {
+        // This will simply get overridden by the next call to OnRawMousePositionEvent, but there's
+        // not much we can do about it, and Barrier mouse input is only for debug purposes anyway.
+        m_systemCursorPositionNormalized = positionNormalized;
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    AZ::Vector2 InputDeviceMouseBarrier::GetSystemCursorPositionNormalized() const
+    {
+        return m_systemCursorPositionNormalized;
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    void InputDeviceMouseBarrier::TickInputDevice()
+    {
+        {
+            // Queue all mouse button events that were received in the other thread
+            AZStd::scoped_lock lock(m_threadAwareRawButtonEventQueuesByIdMutex);
+            for (const auto& buttonEventQueuesById : m_threadAwareRawButtonEventQueuesById)
+            {
+                const InputChannelId& inputChannelId = buttonEventQueuesById.first;
+                for (bool rawButtonState : buttonEventQueuesById.second)
+                {
+                    QueueRawButtonEvent(inputChannelId, rawButtonState);
+                }
+            }
+            m_threadAwareRawButtonEventQueuesById.clear();
+        }
+
+        bool receivedRawMovementEvents = false;
+        {
+            // Queue all mouse movement events that were received in the other thread
+            AZStd::scoped_lock lock(m_threadAwareRawMovementEventQueuesByIdMutex);
+            for (const auto& movementEventQueuesById : m_threadAwareRawMovementEventQueuesById)
+            {
+                const InputChannelId& inputChannelId = movementEventQueuesById.first;
+                for (float rawMovementDelta : movementEventQueuesById.second)
+                {
+                    QueueRawMovementEvent(inputChannelId, rawMovementDelta);
+                    receivedRawMovementEvents = true;
+                }
+            }
+            m_threadAwareRawMovementEventQueuesById.clear();
+        }
+
+        // Update the system cursor position
+        auto atomViewportRequests = AZ::Interface<AZ::RPI::ViewportContextRequestsInterface>::Get();
+        AZ::RPI::ViewportContextPtr viewportContext = atomViewportRequests->GetDefaultViewportContext();
+        if (viewportContext)
+        {
+            const AzFramework::WindowSize windowSize = viewportContext->GetViewportSize();
+            const float windowWidth = static_cast<float>(windowSize.m_width);
+            const float windowHeight = static_cast<float>(windowSize.m_height);
+            const AZ::Vector2 oldSystemCursorPositionNormalized = m_systemCursorPositionNormalized;
+
+            AZStd::scoped_lock lock(m_threadAwareSystemCursorPositionMutex);
+            {
+                const AZ::Vector2 normalizedPosition(m_threadAwareSystemCursorPosition.GetX() / windowWidth,
+                                                     m_threadAwareSystemCursorPosition.GetY() / windowHeight);
+                m_systemCursorPositionNormalized = normalizedPosition;
+            }
+
+            // In theory Barrier should send relative mouse movement events as 'DMRM' messages, which are
+            // forwarded to InputDeviceMouseBarrier::OnRawMouseMovementEvent, but this does not appear to
+            // be happening, so if we didn't receive any relative mouse movement events this frame we can
+            // just approximate the movement ourselves. Unlike other mouse implementations where movement
+            // events are sent 'raw' before any operating system ballistics/smoothing is applied, Barrier
+            // seems to calculate relative mouse movement events by taking the delta between the previous
+            // system cursor position and the current one, so we should obtain the same result regardless.
+            if (!receivedRawMovementEvents)
+            {
+                const AZ::Vector2 mouseMovementDelta = m_systemCursorPositionNormalized - oldSystemCursorPositionNormalized;
+                QueueRawMovementEvent(InputDeviceMouse::Movement::X, mouseMovementDelta.GetX() * windowWidth);
+                QueueRawMovementEvent(InputDeviceMouse::Movement::Y, mouseMovementDelta.GetY() * windowHeight);
+            }
+        }
+
+        // Process raw event queues once each frame
+        ProcessRawEventQueues();
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    void InputDeviceMouseBarrier::OnRawMouseButtonDownEvent(uint32_t buttonIndex)
+    {
+        ThreadSafeQueueRawButtonEvent(buttonIndex, true);
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    void InputDeviceMouseBarrier::OnRawMouseButtonUpEvent(uint32_t buttonIndex)
+    {
+        ThreadSafeQueueRawButtonEvent(buttonIndex, false);
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    void InputDeviceMouseBarrier::OnRawMouseMovementEvent(float movementX, float movementY)
+    {
+        AZStd::scoped_lock lock(m_threadAwareRawMovementEventQueuesByIdMutex);
+        m_threadAwareRawMovementEventQueuesById[InputDeviceMouse::Movement::X].push_back(movementX);
+        m_threadAwareRawMovementEventQueuesById[InputDeviceMouse::Movement::Y].push_back(movementY);
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    void InputDeviceMouseBarrier::OnRawMousePositionEvent(float positionX,
+                                                          float positionY)
+    {
+        AZStd::scoped_lock lock(m_threadAwareSystemCursorPositionMutex);
+        m_threadAwareSystemCursorPosition = AZ::Vector2(positionX, positionY);
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    void InputDeviceMouseBarrier::ThreadSafeQueueRawButtonEvent(uint32_t buttonIndex,
+                                                                bool rawButtonState)
+    {
+        const InputChannelId* inputChannelId = nullptr;
+        switch (buttonIndex)
+        {
+            case 1: { inputChannelId = &InputDeviceMouse::Button::Left; } break;
+            case 2: { inputChannelId = &InputDeviceMouse::Button::Middle; } break;
+            case 3: { inputChannelId = &InputDeviceMouse::Button::Right; } break;
+        }
+
+        if (inputChannelId)
+        {
+            AZStd::scoped_lock lock(m_threadAwareRawButtonEventQueuesByIdMutex);
+            m_threadAwareRawButtonEventQueuesById[*inputChannelId].push_back(rawButtonState);
+        }
+    }
+} // namespace BarrierInput

+ 105 - 0
Gems/BarrierInput/Code/Source/BarrierInputMouse.h

@@ -0,0 +1,105 @@
+/*
+ * 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
+ *
+ */
+
+#pragma once
+
+#include <BarrierInput/RawInputNotificationBus_Barrier.h>
+
+#include <AzFramework/Input/Devices/Mouse/InputDeviceMouse.h>
+
+#include <AzCore/std/parallel/mutex.h>
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+namespace BarrierInput
+{
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    //! Barrier specific implementation for mouse input devices.
+    class InputDeviceMouseBarrier : public AzFramework::InputDeviceMouse::Implementation
+                                  , public RawInputNotificationBusBarrier::Handler
+    {
+    public:
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        // Allocator
+        AZ_CLASS_ALLOCATOR(InputDeviceMouseBarrier, AZ::SystemAllocator, 0);
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! Custom factory create function
+        //! \param[in] inputDevice Reference to the input device being implemented
+        static Implementation* Create(AzFramework::InputDeviceMouse& inputDevice);
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! Constructor
+        //! \param[in] inputDevice Reference to the input device being implemented
+        InputDeviceMouseBarrier(AzFramework::InputDeviceMouse& inputDevice);
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! Destructor
+        ~InputDeviceMouseBarrier() override;
+
+    private:
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! \ref AzFramework::InputDeviceMouse::Implementation::IsConnected
+        bool IsConnected() const override;
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! \ref AzFramework::InputDeviceMouse::Implementation::SetSystemCursorState
+        void SetSystemCursorState(AzFramework::SystemCursorState systemCursorState) override;
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! \ref AzFramework::InputDeviceMouse::Implementation::GetSystemCursorState
+        AzFramework::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;
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! \ref RawInputNotificationsBarrier::OnRawMouseButtonDownEvent
+        void OnRawMouseButtonDownEvent(uint32_t buttonIndex) override;
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! \ref RawInputNotificationsBarrier::OnRawMouseButtonUpEvent
+        void OnRawMouseButtonUpEvent(uint32_t buttonIndex) override;
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! \ref RawInputNotificationsBarrier::OnRawMouseMovementEvent
+        void OnRawMouseMovementEvent(float movementX, float movementY) override;
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! \ref RawInputNotificationsBarrier::OnRawMousePositionEvent
+        void OnRawMousePositionEvent(float positionX, float positionY) override;
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! Thread safe method to queue raw button events to be processed in the main thread update
+        //! \param[in] buttonIndex The index of the button
+        //! \param[in] rawButtonState The raw button state
+        void ThreadSafeQueueRawButtonEvent(uint32_t buttonIndex, bool rawButtonState);
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        // Variables
+        AzFramework::SystemCursorState m_systemCursorState;
+        AZ::Vector2                    m_systemCursorPositionNormalized;
+
+        RawButtonEventQueueByIdMap     m_threadAwareRawButtonEventQueuesById;
+        AZStd::mutex                   m_threadAwareRawButtonEventQueuesByIdMutex;
+
+        RawMovementEventQueueByIdMap   m_threadAwareRawMovementEventQueuesById;
+        AZStd::mutex                   m_threadAwareRawMovementEventQueuesByIdMutex;
+
+        AZ::Vector2                    m_threadAwareSystemCursorPosition;
+        AZStd::mutex                   m_threadAwareSystemCursorPositionMutex;
+    };
+} // namespace BarrierInput

+ 149 - 0
Gems/BarrierInput/Code/Source/BarrierInputSystemComponent.cpp

@@ -0,0 +1,149 @@
+/*
+ * 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 <BarrierInputSystemComponent.h>
+#include <BarrierInputKeyboard.h>
+#include <BarrierInputMouse.h>
+
+#include <AzCore/Serialization/SerializeContext.h>
+#include <AzCore/Serialization/EditContext.h>
+#include <AzCore/Serialization/EditContextConstants.inl>
+
+#include <AzCore/Console/IConsole.h>
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+namespace BarrierInput
+{
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    template<class T>
+    void OnBarrierConnectionCVarChanged(const T&)
+    {
+        BarrierInputConnectionNotificationBus::Broadcast(&BarrierInputConnectionNotifications::OnBarrierConnectionCVarChanged);
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    AZ_CVAR(AZ::CVarFixedString,
+            barrier_clientScreenName,
+            "",
+            OnBarrierConnectionCVarChanged<AZ::CVarFixedString>,
+            AZ::ConsoleFunctorFlags::DontReplicate,
+            "The Barrier screen name assigned to this client.");
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    AZ_CVAR(AZ::CVarFixedString,
+            barrier_serverHostName,
+            "",
+            OnBarrierConnectionCVarChanged<AZ::CVarFixedString>,
+            AZ::ConsoleFunctorFlags::DontReplicate,
+            "The IP or hostname of the Barrier server to connect to.");
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    AZ_CVAR(AZ::u32,
+            barrier_connectionPort,
+            BarrierClient::DEFAULT_BARRIER_CONNECTION_PORT_NUMBER,
+            OnBarrierConnectionCVarChanged<AZ::u32>,
+            AZ::ConsoleFunctorFlags::DontReplicate,
+            "The port number over which to connect to the Barrier server.");
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    void BarrierInputSystemComponent::Reflect(AZ::ReflectContext* context)
+    {
+        if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context))
+        {
+            serialize->Class<BarrierInputSystemComponent, AZ::Component>()
+                ->Version(0);
+
+            if (AZ::EditContext* ec = serialize->GetEditContext())
+            {
+                ec->Class<BarrierInputSystemComponent>("BarrierInput", "Provides functionality related to Barrier input.")
+                    ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
+                        ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("System"))
+                        ->Attribute(AZ::Edit::Attributes::AutoExpand, true)
+                    ;
+            }
+        }
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    void BarrierInputSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
+    {
+        provided.push_back(AZ_CRC("BarrierInputService"));
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    void BarrierInputSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
+    {
+        incompatible.push_back(AZ_CRC("BarrierInputService"));
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    void BarrierInputSystemComponent::Activate()
+    {
+        TryCreateBarrierClientAndInputDeviceImplementations();
+        BarrierInputConnectionNotificationBus::Handler::BusConnect();
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    void BarrierInputSystemComponent::Deactivate()
+    {
+        BarrierInputConnectionNotificationBus::Handler::BusDisconnect();
+        DestroyBarrierClientAndInputDeviceImplementations();
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    void BarrierInputSystemComponent::OnBarrierConnectionCVarChanged()
+    {
+        TryCreateBarrierClientAndInputDeviceImplementations();
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    void BarrierInputSystemComponent::TryCreateBarrierClientAndInputDeviceImplementations()
+    {
+        // Destroy any existing Barrier client and input device implementations.
+        DestroyBarrierClientAndInputDeviceImplementations();
+        
+        const AZ::CVarFixedString barrierClientScreenNameCVar = static_cast<AZ::CVarFixedString>(barrier_clientScreenName);
+        const AZ::CVarFixedString barrierServerHostNameCVar = static_cast<AZ::CVarFixedString>(barrier_serverHostName);
+        const AZ::u32 barrierConnectionPort = static_cast<AZ::u32>(barrier_connectionPort);
+        if (!barrierClientScreenNameCVar.empty() && !barrierServerHostNameCVar.empty() && barrierConnectionPort)
+        {
+            // Enable the Barrier keyboard/mouse input device implementations.
+            AzFramework::InputDeviceImplementationRequest<AzFramework::InputDeviceKeyboard>::Bus::Event(
+                AzFramework::InputDeviceKeyboard::Id,
+                &AzFramework::InputDeviceImplementationRequest<AzFramework::InputDeviceKeyboard>::SetCustomImplementation,
+                BarrierInput::InputDeviceKeyboardBarrier::Create);
+            AzFramework::InputDeviceImplementationRequest<AzFramework::InputDeviceMouse>::Bus::Event(
+                AzFramework::InputDeviceMouse::Id,
+                &AzFramework::InputDeviceImplementationRequest<AzFramework::InputDeviceMouse>::SetCustomImplementation,
+                BarrierInput::InputDeviceMouseBarrier::Create);
+
+            // Create the Barrier client instance.
+            m_barrierClient = AZStd::make_unique<BarrierClient>(barrierClientScreenNameCVar.c_str(), barrierServerHostNameCVar.c_str(), barrierConnectionPort);
+        }
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    void BarrierInputSystemComponent::DestroyBarrierClientAndInputDeviceImplementations()
+    {
+        if (m_barrierClient)
+        {
+            // Destroy the Barrier client instance.
+            m_barrierClient.reset();
+
+            // Reset to the default keyboard/mouse input device implementations.
+            AzFramework::InputDeviceImplementationRequest<AzFramework::InputDeviceKeyboard>::Bus::Event(
+                AzFramework::InputDeviceKeyboard::Id,
+                &AzFramework::InputDeviceImplementationRequest<AzFramework::InputDeviceKeyboard>::SetCustomImplementation,
+                AzFramework::InputDeviceKeyboard::Implementation::Create);
+            AzFramework::InputDeviceImplementationRequest<AzFramework::InputDeviceMouse>::Bus::Event(
+                AzFramework::InputDeviceMouse::Id,
+                &AzFramework::InputDeviceImplementationRequest<AzFramework::InputDeviceMouse>::SetCustomImplementation,
+                AzFramework::InputDeviceMouse::Implementation::Create);
+        }
+    }
+} // namespace BarrierInput

+ 86 - 0
Gems/BarrierInput/Code/Source/BarrierInputSystemComponent.h

@@ -0,0 +1,86 @@
+/*
+ * 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
+ *
+ */
+#pragma once
+
+#include <BarrierInputClient.h>
+
+#include <AzCore/EBus/EBus.h>
+#include <AzCore/Component/Component.h>
+#include <AzCore/std/smart_ptr/unique_ptr.h>
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+namespace BarrierInput
+{
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    //! EBus interface used to listen for changes to Barrier connection related CVars.
+    class BarrierInputConnectionNotifications : public AZ::EBusTraits
+    {
+    public:
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! Called when a CVar relating to the Barrier input connection changes.
+        virtual void OnBarrierConnectionCVarChanged() {}
+    };
+    using BarrierInputConnectionNotificationBus = AZ::EBus<BarrierInputConnectionNotifications>;
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    //! A system component providing functionality related to Barrier input.
+    class BarrierInputSystemComponent : public AZ::Component
+                                      , public BarrierInputConnectionNotificationBus::Handler
+    {
+    public:
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        // AZ::Component Setup
+        AZ_COMPONENT(BarrierInputSystemComponent, "{720B6420-8A76-46F9-80C7-0DBF0CD467C2}");
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! \ref AZ::ComponentDescriptor::Reflect
+        static void Reflect(AZ::ReflectContext* context);
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! \ref AZ::ComponentDescriptor::GetProvidedServices
+        static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! \ref AZ::ComponentDescriptor::GetIncompatibleServices
+        static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! Default constructor
+        BarrierInputSystemComponent() = default;
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! Default destructor
+        ~BarrierInputSystemComponent() override = default;
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! \ref AZ::Component::Activate
+        void Activate() override;
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! \ref AZ::Component::Deactivate
+        void Deactivate() override;
+
+    protected:
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! \ref BarrierInput::BarrierInputConnectionNotifications::OnBarrierConnectionCVarChanged
+        void OnBarrierConnectionCVarChanged() override;
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! Try to create the Barrier client and input device implementations.
+        void TryCreateBarrierClientAndInputDeviceImplementations();
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! Destroy the Barrier client and input device implementations (if they've been created).
+        void DestroyBarrierClientAndInputDeviceImplementations();
+
+    private:
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! The Barrier client instance.
+        AZStd::unique_ptr<BarrierClient> m_barrierClient;
+    };
+} // namespace BarrierInput

+ 19 - 0
Gems/BarrierInput/Code/barrierinput_files.cmake

@@ -0,0 +1,19 @@
+#
+# 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
+#
+#
+
+set(FILES
+    Include/BarrierInput/RawInputNotificationBus_Barrier.h
+    Source/BarrierInputClient.cpp
+    Source/BarrierInputClient.h
+    Source/BarrierInputKeyboard.cpp
+    Source/BarrierInputKeyboard.h
+    Source/BarrierInputMouse.cpp
+    Source/BarrierInputMouse.h
+    Source/BarrierInputSystemComponent.cpp
+    Source/BarrierInputSystemComponent.h
+)

+ 11 - 0
Gems/BarrierInput/Code/barrierinput_shared_files.cmake

@@ -0,0 +1,11 @@
+#
+# 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
+#
+#
+
+set(FILES
+    Source/BarrierInputModule.cpp
+)

+ 12 - 0
Gems/BarrierInput/gem.json

@@ -0,0 +1,12 @@
+{
+    "gem_name": "BarrierInput",
+    "display_name": "Barrier Input",
+    "license": "Apache-2.0 Or MIT",
+    "origin": "Open 3D Engine - o3de.org",
+    "type": "Code",
+    "summary": "The Barrier Input Gem allows the Open 3D Engine to function as a Barrier client so that it can receive input from a remote Barrier server.",
+    "canonical_tags": ["Gem"],
+    "user_tags": ["Input", "Barrier", "Synergy"],
+    "icon_path": "preview.png",
+    "requirements": ""
+}

+ 3 - 0
Gems/BarrierInput/preview.png

@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:7917fbf6e4e3a89e3432b8f48822b660bb245d2b84bb8efdf9f715593c0973df
+size 38792

+ 1 - 0
engine.json

@@ -19,6 +19,7 @@
         "Gems/AWSCore",
         "Gems/AWSGameLift",
         "Gems/AWSMetrics",
+        "Gems/BarrierInput",
         "Gems/Blast",
         "Gems/Camera",
         "Gems/CameraFramework",