зеркало из https://github.com/microsoft/DirectXTK.git
681 строка
16 KiB
C++
681 строка
16 KiB
C++
//--------------------------------------------------------------------------------------
|
|
// File: Keyboard.cpp
|
|
//
|
|
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT License.
|
|
//
|
|
// http://go.microsoft.com/fwlink/?LinkId=248929
|
|
// http://go.microsoft.com/fwlink/?LinkID=615561
|
|
//--------------------------------------------------------------------------------------
|
|
|
|
#include "pch.h"
|
|
#include "Keyboard.h"
|
|
|
|
#include "PlatformHelpers.h"
|
|
|
|
using namespace DirectX;
|
|
using Microsoft::WRL::ComPtr;
|
|
|
|
static_assert(sizeof(Keyboard::State) == (256 / 8), "Size mismatch for State");
|
|
|
|
#ifdef __clang__
|
|
#pragma clang diagnostic ignored "-Wunused-function"
|
|
#endif
|
|
|
|
namespace
|
|
{
|
|
inline void KeyDown(int key, Keyboard::State& state) noexcept
|
|
{
|
|
if (key < 0 || key > 0xfe)
|
|
return;
|
|
|
|
auto ptr = reinterpret_cast<uint32_t*>(&state);
|
|
|
|
const unsigned int bf = 1u << (key & 0x1f);
|
|
ptr[(key >> 5)] |= bf;
|
|
}
|
|
|
|
inline void KeyUp(int key, Keyboard::State& state) noexcept
|
|
{
|
|
if (key < 0 || key > 0xfe)
|
|
return;
|
|
|
|
auto ptr = reinterpret_cast<uint32_t*>(&state);
|
|
|
|
const unsigned int bf = 1u << (key & 0x1f);
|
|
ptr[(key >> 5)] &= ~bf;
|
|
}
|
|
}
|
|
|
|
|
|
#pragma region Implementations
|
|
#ifdef USING_GAMEINPUT
|
|
|
|
#include <GameInput.h>
|
|
|
|
//======================================================================================
|
|
// GameInput
|
|
//======================================================================================
|
|
|
|
class Keyboard::Impl
|
|
{
|
|
public:
|
|
Impl(Keyboard* owner) :
|
|
mOwner(owner),
|
|
mConnected(0),
|
|
mDeviceToken(0),
|
|
mKeyState{}
|
|
{
|
|
if (s_keyboard)
|
|
{
|
|
throw std::logic_error("Keyboard is a singleton");
|
|
}
|
|
|
|
s_keyboard = this;
|
|
|
|
HRESULT hr = GameInputCreate(mGameInput.GetAddressOf());
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
ThrowIfFailed(mGameInput->RegisterDeviceCallback(
|
|
nullptr,
|
|
GameInputKindKeyboard,
|
|
GameInputDeviceConnected,
|
|
GameInputBlockingEnumeration,
|
|
this,
|
|
OnGameInputDevice,
|
|
&mDeviceToken));
|
|
}
|
|
else
|
|
{
|
|
DebugTrace("ERROR: GameInputCreate [keyboard] failed with %08X\n", static_cast<unsigned int>(hr));
|
|
#ifdef _GAMING_XBOX
|
|
ThrowIfFailed(hr);
|
|
#elif defined(_DEBUG)
|
|
DebugTrace(
|
|
"\t**** Check that the 'GameInput Service' is running on this system. ****\n"
|
|
"\t**** NOTE: No keys will be returned and IsConnected will return false. ****\n"
|
|
);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
Impl(Impl&&) = default;
|
|
Impl& operator= (Impl&&) = default;
|
|
|
|
Impl(Impl const&) = delete;
|
|
Impl& operator= (Impl const&) = delete;
|
|
|
|
~Impl()
|
|
{
|
|
if (mDeviceToken)
|
|
{
|
|
if (mGameInput)
|
|
{
|
|
if (!mGameInput->UnregisterCallback(mDeviceToken, UINT64_MAX))
|
|
{
|
|
DebugTrace("ERROR: GameInput::UnregisterCallback [keyboard] failed");
|
|
}
|
|
}
|
|
|
|
mDeviceToken = 0;
|
|
}
|
|
|
|
s_keyboard = nullptr;
|
|
}
|
|
|
|
void GetState(State& state) const
|
|
{
|
|
state = {};
|
|
|
|
if (!mGameInput)
|
|
return;
|
|
|
|
ComPtr<IGameInputReading> reading;
|
|
if (SUCCEEDED(mGameInput->GetCurrentReading(GameInputKindKeyboard, nullptr, reading.GetAddressOf())))
|
|
{
|
|
uint32_t readCount = reading->GetKeyState(c_MaxSimultaneousKeys, mKeyState);
|
|
for (size_t j = 0; j < readCount; ++j)
|
|
{
|
|
int vk = static_cast<int>(mKeyState[j].virtualKey);
|
|
|
|
// Workaround for known issues with VK_RSHIFT and VK_NUMLOCK
|
|
if (vk == 0)
|
|
{
|
|
switch (mKeyState[j].scanCode)
|
|
{
|
|
case 0xe036: vk = VK_RSHIFT; break;
|
|
case 0xe045: vk = VK_NUMLOCK; break;
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
KeyDown(vk, state);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Reset() noexcept
|
|
{
|
|
}
|
|
|
|
bool IsConnected() const
|
|
{
|
|
return mConnected > 0;
|
|
}
|
|
|
|
Keyboard* mOwner;
|
|
uint32_t mConnected;
|
|
|
|
static Keyboard::Impl* s_keyboard;
|
|
|
|
private:
|
|
static constexpr size_t c_MaxSimultaneousKeys = 16;
|
|
|
|
ComPtr<IGameInput> mGameInput;
|
|
GameInputCallbackToken mDeviceToken;
|
|
|
|
mutable GameInputKeyState mKeyState[c_MaxSimultaneousKeys];
|
|
|
|
static void CALLBACK OnGameInputDevice(
|
|
_In_ GameInputCallbackToken,
|
|
_In_ void * context,
|
|
_In_ IGameInputDevice *,
|
|
_In_ uint64_t,
|
|
_In_ GameInputDeviceStatus currentStatus,
|
|
_In_ GameInputDeviceStatus) noexcept
|
|
{
|
|
auto impl = reinterpret_cast<Keyboard::Impl*>(context);
|
|
|
|
if (currentStatus & GameInputDeviceConnected)
|
|
{
|
|
++impl->mConnected;
|
|
}
|
|
else if (impl->mConnected > 0)
|
|
{
|
|
--impl->mConnected;
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
Keyboard::Impl* Keyboard::Impl::s_keyboard = nullptr;
|
|
|
|
|
|
void Keyboard::ProcessMessage(UINT, WPARAM, LPARAM)
|
|
{
|
|
// GameInput for Keyboard doesn't require Win32 messages, but this simplifies integration.
|
|
}
|
|
|
|
|
|
#elif defined(USING_COREWINDOW)
|
|
|
|
//======================================================================================
|
|
// Windows Store or Universal Windows Platform (UWP) app implementation
|
|
//======================================================================================
|
|
|
|
//
|
|
// For a Windows Store app or Universal Windows Platform (UWP) app, add the following:
|
|
//
|
|
// void App::SetWindow(CoreWindow^ window )
|
|
// {
|
|
// m_keyboard->SetWindow(window);
|
|
// }
|
|
//
|
|
|
|
#include <Windows.Devices.Input.h>
|
|
|
|
class Keyboard::Impl
|
|
{
|
|
public:
|
|
Impl(Keyboard* owner) :
|
|
mState{},
|
|
mOwner(owner),
|
|
mAcceleratorKeyToken{},
|
|
mActivatedToken{}
|
|
{
|
|
if (s_keyboard)
|
|
{
|
|
throw std::logic_error("Keyboard is a singleton");
|
|
}
|
|
|
|
s_keyboard = this;
|
|
}
|
|
|
|
~Impl()
|
|
{
|
|
s_keyboard = nullptr;
|
|
|
|
RemoveHandlers();
|
|
}
|
|
|
|
void GetState(State& state) const
|
|
{
|
|
memcpy(&state, &mState, sizeof(State));
|
|
}
|
|
|
|
void Reset() noexcept
|
|
{
|
|
memset(&mState, 0, sizeof(State));
|
|
}
|
|
|
|
bool IsConnected() const
|
|
{
|
|
using namespace Microsoft::WRL;
|
|
using namespace Microsoft::WRL::Wrappers;
|
|
using namespace ABI::Windows::Devices::Input;
|
|
using namespace ABI::Windows::Foundation;
|
|
|
|
ComPtr<IKeyboardCapabilities> caps;
|
|
HRESULT hr = RoActivateInstance(HStringReference(RuntimeClass_Windows_Devices_Input_KeyboardCapabilities).Get(), &caps);
|
|
ThrowIfFailed(hr);
|
|
|
|
INT32 value;
|
|
if (SUCCEEDED(caps->get_KeyboardPresent(&value)))
|
|
{
|
|
return value != 0;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void SetWindow(ABI::Windows::UI::Core::ICoreWindow* window)
|
|
{
|
|
using namespace Microsoft::WRL;
|
|
using namespace Microsoft::WRL::Wrappers;
|
|
using namespace ABI::Windows::UI::Core;
|
|
|
|
if (mWindow.Get() == window)
|
|
return;
|
|
|
|
RemoveHandlers();
|
|
|
|
mWindow = window;
|
|
|
|
if (!window)
|
|
return;
|
|
|
|
typedef __FITypedEventHandler_2_Windows__CUI__CCore__CCoreWindow_Windows__CUI__CCore__CWindowActivatedEventArgs ActivatedHandler;
|
|
HRESULT hr = window->add_Activated(Callback<ActivatedHandler>(Activated).Get(), &mActivatedToken);
|
|
ThrowIfFailed(hr);
|
|
|
|
ComPtr<ICoreDispatcher> dispatcher;
|
|
hr = window->get_Dispatcher(dispatcher.GetAddressOf());
|
|
ThrowIfFailed(hr);
|
|
|
|
ComPtr<ICoreAcceleratorKeys> keys;
|
|
hr = dispatcher.As(&keys);
|
|
ThrowIfFailed(hr);
|
|
|
|
typedef __FITypedEventHandler_2_Windows__CUI__CCore__CCoreDispatcher_Windows__CUI__CCore__CAcceleratorKeyEventArgs AcceleratorKeyHandler;
|
|
hr = keys->add_AcceleratorKeyActivated(Callback<AcceleratorKeyHandler>(AcceleratorKeyEvent).Get(), &mAcceleratorKeyToken);
|
|
ThrowIfFailed(hr);
|
|
}
|
|
|
|
State mState;
|
|
Keyboard* mOwner;
|
|
|
|
static Keyboard::Impl* s_keyboard;
|
|
|
|
private:
|
|
ComPtr<ABI::Windows::UI::Core::ICoreWindow> mWindow;
|
|
|
|
EventRegistrationToken mAcceleratorKeyToken;
|
|
EventRegistrationToken mActivatedToken;
|
|
|
|
void RemoveHandlers()
|
|
{
|
|
if (mWindow)
|
|
{
|
|
using namespace ABI::Windows::UI::Core;
|
|
|
|
ComPtr<ICoreDispatcher> dispatcher;
|
|
HRESULT hr = mWindow->get_Dispatcher(dispatcher.GetAddressOf());
|
|
ThrowIfFailed(hr);
|
|
|
|
std::ignore = mWindow->remove_Activated(mActivatedToken);
|
|
mActivatedToken.value = 0;
|
|
|
|
ComPtr<ICoreAcceleratorKeys> keys;
|
|
hr = dispatcher.As(&keys);
|
|
ThrowIfFailed(hr);
|
|
|
|
std::ignore = keys->remove_AcceleratorKeyActivated(mAcceleratorKeyToken);
|
|
mAcceleratorKeyToken.value = 0;
|
|
}
|
|
}
|
|
|
|
static HRESULT Activated(IInspectable*, ABI::Windows::UI::Core::IWindowActivatedEventArgs*)
|
|
{
|
|
auto pImpl = Impl::s_keyboard;
|
|
|
|
if (!pImpl)
|
|
return S_OK;
|
|
|
|
pImpl->Reset();
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
static HRESULT AcceleratorKeyEvent(IInspectable*, ABI::Windows::UI::Core::IAcceleratorKeyEventArgs* args)
|
|
{
|
|
using namespace ABI::Windows::System;
|
|
using namespace ABI::Windows::UI::Core;
|
|
|
|
auto pImpl = Impl::s_keyboard;
|
|
|
|
if (!pImpl)
|
|
return S_OK;
|
|
|
|
CoreAcceleratorKeyEventType evtType;
|
|
HRESULT hr = args->get_EventType(&evtType);
|
|
ThrowIfFailed(hr);
|
|
|
|
bool down = false;
|
|
|
|
switch (evtType)
|
|
{
|
|
case CoreAcceleratorKeyEventType_KeyDown:
|
|
case CoreAcceleratorKeyEventType_SystemKeyDown:
|
|
down = true;
|
|
break;
|
|
|
|
case CoreAcceleratorKeyEventType_KeyUp:
|
|
case CoreAcceleratorKeyEventType_SystemKeyUp:
|
|
break;
|
|
|
|
default:
|
|
return S_OK;
|
|
}
|
|
|
|
CorePhysicalKeyStatus status;
|
|
hr = args->get_KeyStatus(&status);
|
|
ThrowIfFailed(hr);
|
|
|
|
VirtualKey virtualKey;
|
|
hr = args->get_VirtualKey(&virtualKey);
|
|
ThrowIfFailed(hr);
|
|
|
|
int vk = static_cast<int>(virtualKey);
|
|
|
|
switch (vk)
|
|
{
|
|
case VK_SHIFT:
|
|
vk = (status.ScanCode == 0x36) ? VK_RSHIFT : VK_LSHIFT;
|
|
if (!down)
|
|
{
|
|
// Workaround to ensure left vs. right shift get cleared when both were pressed at same time
|
|
KeyUp(VK_LSHIFT, pImpl->mState);
|
|
KeyUp(VK_RSHIFT, pImpl->mState);
|
|
}
|
|
break;
|
|
|
|
case VK_CONTROL:
|
|
vk = (status.IsExtendedKey) ? VK_RCONTROL : VK_LCONTROL;
|
|
break;
|
|
|
|
case VK_MENU:
|
|
vk = (status.IsExtendedKey) ? VK_RMENU : VK_LMENU;
|
|
break;
|
|
}
|
|
|
|
if (down)
|
|
{
|
|
KeyDown(vk, pImpl->mState);
|
|
}
|
|
else
|
|
{
|
|
KeyUp(vk, pImpl->mState);
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
};
|
|
|
|
|
|
Keyboard::Impl* Keyboard::Impl::s_keyboard = nullptr;
|
|
|
|
|
|
void Keyboard::SetWindow(ABI::Windows::UI::Core::ICoreWindow* window)
|
|
{
|
|
pImpl->SetWindow(window);
|
|
}
|
|
|
|
|
|
#else
|
|
|
|
//======================================================================================
|
|
// Win32 desktop implementation
|
|
//======================================================================================
|
|
|
|
//
|
|
// For a Win32 desktop application, call this function from your Window Message Procedure
|
|
//
|
|
// LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
|
|
// {
|
|
// switch (message)
|
|
// {
|
|
//
|
|
// case WM_ACTIVATE:
|
|
// case WM_ACTIVATEAPP:
|
|
// Keyboard::ProcessMessage(message, wParam, lParam);
|
|
// break;
|
|
//
|
|
// case WM_KEYDOWN:
|
|
// case WM_SYSKEYDOWN:
|
|
// case WM_KEYUP:
|
|
// case WM_SYSKEYUP:
|
|
// Keyboard::ProcessMessage(message, wParam, lParam);
|
|
// break;
|
|
//
|
|
// }
|
|
// }
|
|
//
|
|
|
|
class Keyboard::Impl
|
|
{
|
|
public:
|
|
Impl(Keyboard* owner) :
|
|
mState{},
|
|
mOwner(owner)
|
|
{
|
|
if (s_keyboard)
|
|
{
|
|
throw std::logic_error("Keyboard is a singleton");
|
|
}
|
|
|
|
s_keyboard = this;
|
|
}
|
|
|
|
Impl(Impl&&) = default;
|
|
Impl& operator= (Impl&&) = default;
|
|
|
|
Impl(Impl const&) = delete;
|
|
Impl& operator= (Impl const&) = delete;
|
|
|
|
~Impl()
|
|
{
|
|
s_keyboard = nullptr;
|
|
}
|
|
|
|
void GetState(State& state) const
|
|
{
|
|
memcpy(&state, &mState, sizeof(State));
|
|
}
|
|
|
|
void Reset() noexcept
|
|
{
|
|
memset(&mState, 0, sizeof(State));
|
|
}
|
|
|
|
bool IsConnected() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
State mState;
|
|
Keyboard* mOwner;
|
|
|
|
static Keyboard::Impl* s_keyboard;
|
|
};
|
|
|
|
|
|
Keyboard::Impl* Keyboard::Impl::s_keyboard = nullptr;
|
|
|
|
|
|
void Keyboard::ProcessMessage(UINT message, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
auto pImpl = Impl::s_keyboard;
|
|
|
|
if (!pImpl)
|
|
return;
|
|
|
|
bool down = false;
|
|
|
|
switch (message)
|
|
{
|
|
case WM_ACTIVATE:
|
|
case WM_ACTIVATEAPP:
|
|
pImpl->Reset();
|
|
return;
|
|
|
|
case WM_KEYDOWN:
|
|
case WM_SYSKEYDOWN:
|
|
down = true;
|
|
break;
|
|
|
|
case WM_KEYUP:
|
|
case WM_SYSKEYUP:
|
|
break;
|
|
|
|
default:
|
|
return;
|
|
}
|
|
|
|
int vk = LOWORD(wParam);
|
|
// We want to distinguish left and right shift/ctrl/alt keys
|
|
switch (vk)
|
|
{
|
|
case VK_SHIFT:
|
|
case VK_CONTROL:
|
|
case VK_MENU:
|
|
{
|
|
if (vk == VK_SHIFT && !down)
|
|
{
|
|
// Workaround to ensure left vs. right shift get cleared when both were pressed at same time
|
|
KeyUp(VK_LSHIFT, pImpl->mState);
|
|
KeyUp(VK_RSHIFT, pImpl->mState);
|
|
}
|
|
|
|
bool isExtendedKey = (HIWORD(lParam) & KF_EXTENDED) == KF_EXTENDED;
|
|
int scanCode = LOBYTE(HIWORD(lParam)) | (isExtendedKey ? 0xe000 : 0);
|
|
vk = LOWORD(MapVirtualKeyW(static_cast<UINT>(scanCode), MAPVK_VSC_TO_VK_EX));
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (down)
|
|
{
|
|
KeyDown(vk, pImpl->mState);
|
|
}
|
|
else
|
|
{
|
|
KeyUp(vk, pImpl->mState);
|
|
}
|
|
}
|
|
|
|
#endif
|
|
#pragma endregion
|
|
|
|
#ifdef _MSC_VER
|
|
#pragma warning( disable : 4355 )
|
|
#endif
|
|
|
|
// Public constructor.
|
|
Keyboard::Keyboard() noexcept(false)
|
|
: pImpl(std::make_unique<Impl>(this))
|
|
{
|
|
}
|
|
|
|
|
|
// Move constructor.
|
|
Keyboard::Keyboard(Keyboard&& moveFrom) noexcept
|
|
: pImpl(std::move(moveFrom.pImpl))
|
|
{
|
|
pImpl->mOwner = this;
|
|
}
|
|
|
|
|
|
// Move assignment.
|
|
Keyboard& Keyboard::operator= (Keyboard&& moveFrom) noexcept
|
|
{
|
|
pImpl = std::move(moveFrom.pImpl);
|
|
pImpl->mOwner = this;
|
|
return *this;
|
|
}
|
|
|
|
|
|
// Public destructor.
|
|
Keyboard::~Keyboard() = default;
|
|
|
|
|
|
Keyboard::State Keyboard::GetState() const
|
|
{
|
|
State state;
|
|
pImpl->GetState(state);
|
|
return state;
|
|
}
|
|
|
|
|
|
void Keyboard::Reset() noexcept
|
|
{
|
|
pImpl->Reset();
|
|
}
|
|
|
|
|
|
bool Keyboard::IsConnected() const
|
|
{
|
|
return pImpl->IsConnected();
|
|
}
|
|
|
|
Keyboard& Keyboard::Get()
|
|
{
|
|
if (!Impl::s_keyboard || !Impl::s_keyboard->mOwner)
|
|
throw std::logic_error("Keyboard singleton not created");
|
|
|
|
return *Impl::s_keyboard->mOwner;
|
|
}
|
|
|
|
|
|
|
|
//======================================================================================
|
|
// KeyboardStateTracker
|
|
//======================================================================================
|
|
|
|
void Keyboard::KeyboardStateTracker::Update(const State& state) noexcept
|
|
{
|
|
auto currPtr = reinterpret_cast<const uint32_t*>(&state);
|
|
auto prevPtr = reinterpret_cast<const uint32_t*>(&lastState);
|
|
auto releasedPtr = reinterpret_cast<uint32_t*>(&released);
|
|
auto pressedPtr = reinterpret_cast<uint32_t*>(&pressed);
|
|
for (size_t j = 0; j < (256 / 32); ++j)
|
|
{
|
|
*pressedPtr = *currPtr & ~(*prevPtr);
|
|
*releasedPtr = ~(*currPtr) & *prevPtr;
|
|
|
|
++currPtr;
|
|
++prevPtr;
|
|
++releasedPtr;
|
|
++pressedPtr;
|
|
}
|
|
|
|
lastState = state;
|
|
}
|
|
|
|
void Keyboard::KeyboardStateTracker::Reset() noexcept
|
|
{
|
|
memset(this, 0, sizeof(KeyboardStateTracker));
|
|
}
|