Files
o3de/Gems/BarrierInput/Code/Source/BarrierInputKeyboard.cpp
T
bosnichd 90e45a5bbd 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 <bosnichd@amazon.com>
2021-07-26 12:19:00 -06:00

261 lines
11 KiB
C++

/*
* 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