Initial commit

This commit is contained in:
alexpete
2021-03-05 11:26:34 -08:00
commit a10351f38d
27091 changed files with 5521199 additions and 0 deletions
@@ -0,0 +1,14 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzFramework/API/ApplicationAPI_Windows.h>
@@ -0,0 +1,40 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/EBus/EBus.h>
namespace AzFramework
{
class WindowsLifecycleEvents
: public AZ::EBusTraits
{
public:
// Bus Configuration
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
virtual ~WindowsLifecycleEvents() {}
using Bus = AZ::EBus<WindowsLifecycleEvents>;
virtual void OnMinimized() {}
virtual void OnMaximized() {}
virtual void OnRestored() {}
virtual void OnKillFocus() {}
virtual void OnSetFocus() {}
};
} // namespace AzFramework
@@ -0,0 +1,148 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/PlatformIncl.h>
#include <AzFramework/API/ApplicationAPI_Platform.h>
#include <AzFramework/Application/Application.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace AzFramework
{
////////////////////////////////////////////////////////////////////////////////////////////////
class ApplicationWindows
: public Application::Implementation
, public WindowsLifecycleEvents::Bus::Handler
{
public:
////////////////////////////////////////////////////////////////////////////////////////////
AZ_CLASS_ALLOCATOR(ApplicationWindows, AZ::SystemAllocator, 0);
ApplicationWindows();
~ApplicationWindows() override;
////////////////////////////////////////////////////////////////////////////////////////////
// WindowsLifecycleEvents
void OnMinimized() override; // Suspend
void OnRestored() override; // Resume
void OnKillFocus() override; // Constrain
void OnSetFocus() override; // Unconstrain
////////////////////////////////////////////////////////////////////////////////////////////
// Application::Implementation
void PumpSystemEventLoopOnce() override;
void PumpSystemEventLoopUntilEmpty() override;
protected:
void ProcessSystemEvent(MSG& msg);
private:
ApplicationLifecycleEvents::Event m_lastEvent;
};
////////////////////////////////////////////////////////////////////////////////////////////////
Application::Implementation* Application::Implementation::Create()
{
return aznew ApplicationWindows();
}
////////////////////////////////////////////////////////////////////////////////////////////////
ApplicationWindows::ApplicationWindows()
: m_lastEvent(ApplicationLifecycleEvents::Event::None)
{
WindowsLifecycleEvents::Bus::Handler::BusConnect();
}
////////////////////////////////////////////////////////////////////////////////////////////////
ApplicationWindows::~ApplicationWindows()
{
WindowsLifecycleEvents::Bus::Handler::BusDisconnect();
}
////////////////////////////////////////////////////////////////////////////////////////////////
void ApplicationWindows::OnMinimized()
{
// guard against duplicate events
if (m_lastEvent != ApplicationLifecycleEvents::Event::Suspend)
{
EBUS_EVENT(ApplicationLifecycleEvents::Bus, OnApplicationSuspended, m_lastEvent);
m_lastEvent = ApplicationLifecycleEvents::Event::Suspend;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
void ApplicationWindows::OnRestored()
{
// guard against duplicate events
if (m_lastEvent != ApplicationLifecycleEvents::Event::Resume)
{
EBUS_EVENT(ApplicationLifecycleEvents::Bus, OnApplicationResumed, m_lastEvent);
m_lastEvent = ApplicationLifecycleEvents::Event::Resume;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
void ApplicationWindows::OnKillFocus()
{
// guard against duplicate events
if (m_lastEvent != ApplicationLifecycleEvents::Event::Constrain)
{
EBUS_EVENT(ApplicationLifecycleEvents::Bus, OnApplicationConstrained, m_lastEvent);
m_lastEvent = ApplicationLifecycleEvents::Event::Constrain;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
void ApplicationWindows::OnSetFocus()
{
// guard against duplicate events
if (m_lastEvent != ApplicationLifecycleEvents::Event::Unconstrain)
{
EBUS_EVENT(ApplicationLifecycleEvents::Bus, OnApplicationUnconstrained, m_lastEvent);
m_lastEvent = ApplicationLifecycleEvents::Event::Unconstrain;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
void ApplicationWindows::PumpSystemEventLoopOnce()
{
MSG msg;
if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE))
{
ProcessSystemEvent(msg);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
void ApplicationWindows::PumpSystemEventLoopUntilEmpty()
{
MSG msg;
while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE))
{
ProcessSystemEvent(msg);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
void ApplicationWindows::ProcessSystemEvent(MSG& msg)
{
if (msg.message == WM_QUIT)
{
ApplicationRequests::Bus::Broadcast(&ApplicationRequests::ExitMainLoop);
}
else
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
} // namespace AzFramework
@@ -0,0 +1,14 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzFramework/Archive/ArchiveVars_Windows.h>
@@ -0,0 +1,16 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#define STREAM_CACHE_DEFAULT 0
#define FRONTEND_SHADER_CACHE_DEFAULT 0
@@ -0,0 +1,102 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/PlatformIncl.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include <Psapi.h>
namespace AzFramework::AssetSystem::Platform
{
void AllowAssetProcessorToForeground()
{
// Make sure that all of the asset processors can bring their window to the front
// Hacky to put it here, but not really any better place.
DWORD bytesReturned;
// There's no straightforward way to get the exact number of running processes,
// So we use 2^13 processes as a generous upper bound that shouldn't be hit.
DWORD processIds[8 * 1024];
if (EnumProcesses(processIds, sizeof(processIds), &bytesReturned))
{
const DWORD numProcesses = bytesReturned / sizeof(DWORD);
for (DWORD processIndex = 0; processIndex < numProcesses; ++processIndex)
{
DWORD processId = processIds[processIndex];
HANDLE processHandle = OpenProcess(PROCESS_QUERY_INFORMATION |
PROCESS_VM_READ,
FALSE, processId);
// Get the process name.
if (processHandle)
{
HMODULE moduleHandle;
DWORD bytesNeededForAllProcessModules;
// Get the first module, because that will be the executable
if (EnumProcessModules(processHandle, &moduleHandle, sizeof(moduleHandle), &bytesNeededForAllProcessModules))
{
char processName[4096] = TEXT("<unknown>");
if (GetModuleBaseNameA(processHandle, moduleHandle, processName, AZ_ARRAY_SIZE(processName)) > 0)
{
if (azstricmp(processName, "AssetProcessor") == 0)
{
AllowSetForegroundWindow(processId);
}
}
}
}
}
}
}
bool LaunchAssetProcessor(AZStd::string_view executableDirectory, AZStd::string_view appRoot,
AZStd::string_view gameProjectName)
{
AZ::IO::FixedMaxPath assetProcessorPath{ executableDirectory };
assetProcessorPath /= "AssetProcessor.exe";
auto fullLaunchCommand = AZ::IO::FixedMaxPathString::format(R"("%s" --start-hidden)", assetProcessorPath.c_str());
// Add the app-root to the launch command if not empty
if (!appRoot.empty())
{
fullLaunchCommand += R"( --app-root=")";
fullLaunchCommand += appRoot;
// Windows CreateProcess has issues with paths that end with a trailing backslash
// so remove it if it exist
if (fullLaunchCommand.ends_with(AZ::IO::WindowsPathSeparator))
{
fullLaunchCommand.pop_back();
}
fullLaunchCommand += '"';
}
// Add the active game project to the launch command if not empty
if (!gameProjectName.empty())
{
fullLaunchCommand += R"( --gameFolder=")";
fullLaunchCommand += gameProjectName;
fullLaunchCommand += '"';
}
STARTUPINFO si;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = SW_MINIMIZE;
PROCESS_INFORMATION pi;
return ::CreateProcessA(nullptr, fullLaunchCommand.data(), nullptr, nullptr, FALSE, 0, nullptr, AZ::IO::FixedMaxPathString{ executableDirectory }.c_str(), &si, &pi) != 0;
}
}
@@ -0,0 +1,14 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzFramework/AzFramework_Traits_Windows.h>
@@ -0,0 +1,18 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#define AZ_TRAIT_AZFRAMEWORK_AWS_ENABLE_TCP_KEEP_ALIVE_SUPPORTED (false)
#define AZ_TRAIT_AZFRAMEWORK_BOOTSTRAP_CFG_CURRENT_PLATFORM "windows"
#define AZ_TRAIT_AZFRAMEWORK_USE_C11_LOCALTIME_S 1
#define AZ_TRAIT_AZFRAMEWORK_USE_ERRNO_T_TYPEDEF 0
#define AZ_TRAIT_AZFRAMEWORK_USE_POSIX_LOCALTIME_R 0
@@ -0,0 +1,36 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/PlatformIncl.h>
#include <AzFramework/IO/LocalFileIO.h>
#include <AzCore/IO/SystemFile.h>
namespace AZ
{
namespace IO
{
Result LocalFileIO::Copy(const char* sourceFilePath, const char* destinationFilePath)
{
char resolvedSourcePath[AZ_MAX_PATH_LEN];
ResolvePath(sourceFilePath, resolvedSourcePath, AZ_MAX_PATH_LEN);
char resolvedDestPath[AZ_MAX_PATH_LEN];
ResolvePath(destinationFilePath, resolvedDestPath, AZ_MAX_PATH_LEN);
if (::CopyFileA(resolvedSourcePath, resolvedDestPath, false) == 0)
{
return ResultCode::Error;
}
return ResultCode::Success;
}
} // namespace IO
}//namespace AZ
@@ -0,0 +1,14 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzFramework/Input/Buses/Notifications/RawInputNotificationBus_Windows.h>
@@ -0,0 +1,67 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/EBus/EBus.h>
typedef struct tagRAWINPUT RAWINPUT;
typedef struct tagRID_DEVICE_INFO RID_DEVICE_INFO;
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace AzFramework
{
////////////////////////////////////////////////////////////////////////////////////////////////
//! EBus interface used to listen for raw Windows input events sent by the system. Applications
//! that want RawInput events to be processed by the AzFramework input system must broadcast all
//! WM_INPUT events received by the system's WndProc, which is the lowest level we can get input.
//!
//! It's possible to receive multiple events per button/key per frame, and (depending on how the
//! Windows event loop is pumped) it is also possible that events could be sent from any thread,
//! however it is assumed they will always be sent from the WndProc function on the main thread.
//!
//! This EBus is intended primarily for the AzFramework input system to process Windows events.
//! 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 Windows events.
class RawInputNotificationsWindows : 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 ~RawInputNotificationsWindows() = default;
////////////////////////////////////////////////////////////////////////////////////////////
//! Process raw input events (assumed to be dispatched on the main thread)
//! \param[in] rawInput The raw input data
virtual void OnRawInputEvent(const RAWINPUT& /*rawInput*/) {}
////////////////////////////////////////////////////////////////////////////////////////////
//! Process raw input device connection events (assumed to be dispatched on the main thread)
virtual void OnRawInputDeviceChangeEvent() {}
////////////////////////////////////////////////////////////////////////////////////////////
//! Process raw WM_CHAR events (assumed to be dispatched on the main thread)
//! \param[in] codeUnitUTF16 The UTF16 unicode code-unit. Note that this may not correspond
//! directly to a single UTF16 code-point (or character); each individual event may be part
//! of a two code-unit 'surrogate pair' that together defines a single UTF16 code-point.
virtual void OnRawInputCodeUnitUTF16Event(uint16_t /*codeUnitUTF16*/) {}
};
using RawInputNotificationBusWindows = AZ::EBus<RawInputNotificationsWindows>;
} // namespace AzFramework
@@ -0,0 +1,364 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/PlatformIncl.h>
#include <AzFramework/Input/Devices/Gamepad/InputDeviceGamepad.h>
#include <AzFramework/Input/Buses/Notifications/RawInputNotificationBus_Platform.h>
#include <AzCore/Debug/Trace.h>
#include <AzCore/Module/DynamicModuleHandle.h>
#include <AzCore/std/smart_ptr/shared_ptr.h>
#include <AzCore/std/smart_ptr/weak_ptr.h>
#include <xinput.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace
{
using namespace AzFramework;
////////////////////////////////////////////////////////////////////////////////////////////////
//! Map of digital button ids keyed by their xinput button bitmask
const AZStd::unordered_map<AZ::u32, const InputChannelId*> GetDigitalButtonIdByBitMaskMap()
{
const AZStd::unordered_map<AZ::u32, const InputChannelId*> map =
{
{ XINPUT_GAMEPAD_DPAD_UP, &InputDeviceGamepad::Button::DU }, // 0x0001
{ XINPUT_GAMEPAD_DPAD_DOWN, &InputDeviceGamepad::Button::DD }, // 0x0002
{ XINPUT_GAMEPAD_DPAD_LEFT, &InputDeviceGamepad::Button::DL }, // 0x0004
{ XINPUT_GAMEPAD_DPAD_RIGHT, &InputDeviceGamepad::Button::DR }, // 0x0008
{ XINPUT_GAMEPAD_START, &InputDeviceGamepad::Button::Start }, // 0x0010
{ XINPUT_GAMEPAD_BACK, &InputDeviceGamepad::Button::Select },// 0x0020
{ XINPUT_GAMEPAD_LEFT_THUMB, &InputDeviceGamepad::Button::L3 }, // 0x0040
{ XINPUT_GAMEPAD_RIGHT_THUMB, &InputDeviceGamepad::Button::R3 }, // 0x0080
{ XINPUT_GAMEPAD_LEFT_SHOULDER, &InputDeviceGamepad::Button::L1 }, // 0x0100
{ XINPUT_GAMEPAD_RIGHT_SHOULDER,&InputDeviceGamepad::Button::R1 }, // 0x0200
{ XINPUT_GAMEPAD_A, &InputDeviceGamepad::Button::A }, // 0x1000
{ XINPUT_GAMEPAD_B, &InputDeviceGamepad::Button::B }, // 0x2000
{ XINPUT_GAMEPAD_X, &InputDeviceGamepad::Button::X }, // 0x4000
{ XINPUT_GAMEPAD_Y, &InputDeviceGamepad::Button::Y } // 0x8000
};
return map;
}
////////////////////////////////////////////////////////////////////////////////////////////////
//! The maximum value and dead zone of analog trigger buttons
const float AnalogTriggerMaxValue = 255.0f;
const float AnalogTriggerDeadZone = XINPUT_GAMEPAD_TRIGGER_THRESHOLD;
////////////////////////////////////////////////////////////////////////////////////////////////
//! The maximum value and radial dead zones of the left and right thumb-sticks
const float ThumbStickMaxValue = 32767.0f;
const float ThumbStickLeftDeadZone = XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE;
const float ThumbStickRightDeadZone = XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE;
////////////////////////////////////////////////////////////////////////////////////////////////
//! The maximum rotation value of the left (large) and right (small) force-feedback motors
const float VibrationMaxValue = 65535.0f;
} // namespace
////////////////////////////////////////////////////////////////////////////////////////////////////
//! Unfortunately, the XInput library doesn't ship with Windows Server 2012, so we're forced to load
//! it explicitly at run-time (instead of simply linking against it), and handle it not being found.
//! https://msdn.microsoft.com/en-us/library/windows/desktop/hh405051(v=vs.85).aspx
namespace XInput
{
////////////////////////////////////////////////////////////////////////////////////////////////
//! Name of the XInput dynamic module
const char* DynamicModuleName = "XInput9_1_0";
////////////////////////////////////////////////////////////////////////////////////////////////
//! Weak handle to the XInput dynamic module
AZStd::weak_ptr<AZ::DynamicModuleHandle> WeakHandleToDynamicModule;
////////////////////////////////////////////////////////////////////////////////////////////////
//! Name of the XInputGetState function
const char* GetStateFunctionName = "XInputGetState";
////////////////////////////////////////////////////////////////////////////////////////////////
//! Type/signature of the XInputGetState function
using GetStateFunctionType = DWORD(*)(DWORD, XINPUT_STATE*);
////////////////////////////////////////////////////////////////////////////////////////////////
//! Function pointer to the XInputGetState function
GetStateFunctionType GetStateFunctionPointer = nullptr;
////////////////////////////////////////////////////////////////////////////////////////////////
//! Name of the XInputSetState function
const char* SetStateFunctionName = "XInputSetState";
////////////////////////////////////////////////////////////////////////////////////////////////
//! Type/signature of the XInputSetState function
using SetStateFunctionType = DWORD(*)(DWORD, XINPUT_VIBRATION*);
////////////////////////////////////////////////////////////////////////////////////////////////
//! Function pointer to the XInputSetState function
SetStateFunctionType SetStateFunctionPointer = nullptr;
////////////////////////////////////////////////////////////////////////////////////////////////
//! Load the XInput dynamic module
//! \return A shared pointer to the XInput dynamic module. May be null if it could not be loaded.
AZStd::shared_ptr<AZ::DynamicModuleHandle> LoadDynamicModule()
{
if (!WeakHandleToDynamicModule.expired())
{
return WeakHandleToDynamicModule.lock();
}
AZStd::shared_ptr<AZ::DynamicModuleHandle> handle = AZ::DynamicModuleHandle::Create(DynamicModuleName);
const bool loaded = handle->Load(false);
if (!loaded)
{
// Could not load XInput9_1_0, this is most likely a Windows Server 2012 machine.
return nullptr;
}
GetStateFunctionPointer = handle->GetFunction<GetStateFunctionType>(GetStateFunctionName);
if (!GetStateFunctionPointer)
{
AZ_Assert(false, "Could not find %s function in %", GetStateFunctionName, DynamicModuleName);
return nullptr;
}
SetStateFunctionPointer = handle->GetFunction<SetStateFunctionType>(SetStateFunctionName);
if (!SetStateFunctionPointer)
{
AZ_Assert(false, "Could not find %s function in %", SetStateFunctionName, DynamicModuleName);
GetStateFunctionPointer = nullptr;
return nullptr;
}
WeakHandleToDynamicModule = handle;
return handle;
}
} // namespace
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace AzFramework
{
////////////////////////////////////////////////////////////////////////////////////////////////
//! Platform specific implementation for Windows game-pad input devices
class InputDeviceGamepadWindows : public InputDeviceGamepad::Implementation
, public RawInputNotificationBusWindows::Handler
{
public:
////////////////////////////////////////////////////////////////////////////////////////////
// Allocator
AZ_CLASS_ALLOCATOR(InputDeviceGamepadWindows, AZ::SystemAllocator, 0);
////////////////////////////////////////////////////////////////////////////////////////////
//! Constructor
//! \param[in] inputDevice Reference to the input device being implemented
//! \param[in] xinputModuleHandle Shared pointer to the xinput dynamic module
InputDeviceGamepadWindows(InputDeviceGamepad& inputDevice,
AZStd::shared_ptr<AZ::DynamicModuleHandle> xinputModuleHandle);
////////////////////////////////////////////////////////////////////////////////////////////
//! Destructor
~InputDeviceGamepadWindows() override;
private:
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceGamepad::Implementation::IsConnected
bool IsConnected() const override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceGamepad::Implementation::SetVibration
void SetVibration(float leftMotorSpeedNormalized, float rightMotorSpeedNormalized) override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceGamepad::Implementation::GetPhysicalKeyOrButtonText
bool GetPhysicalKeyOrButtonText(const InputChannelId& inputChannelId,
AZStd::string& o_keyOrButtonText) const override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceGamepad::Implementation::TickInputDevice
void TickInputDevice() override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::RawInputNotificationsWindows::OnRawInputDeviceChangeEvent
void OnRawInputDeviceChangeEvent() override;
////////////////////////////////////////////////////////////////////////////////////////////
// Variables
AZStd::shared_ptr<AZ::DynamicModuleHandle> m_xinputModuleHandle; //!< Handle to the xinput module
RawGamepadState m_rawGamepadState; //!< The last known raw game-pad state
bool m_isConnected; //!< Is this game-pad currently connected?
bool m_tryConnect; //!< Check whether this game-pad just connected?
};
////////////////////////////////////////////////////////////////////////////////////////////////
AZ::u32 InputDeviceGamepad::GetMaxSupportedGamepads()
{
return XUSER_MAX_COUNT;
}
////////////////////////////////////////////////////////////////////////////////////////////////
InputDeviceGamepad::Implementation* InputDeviceGamepad::Implementation::Create(
InputDeviceGamepad& inputDevice)
{
// Before creating any instances of InputDeviceGamepadWindows, ensure that XInput is loaded
AZStd::shared_ptr<AZ::DynamicModuleHandle> xinputModuleHandle = XInput::LoadDynamicModule();
if (!xinputModuleHandle)
{
// Could not load XInput9_1_0, this is most likely a Windows Server 2012 machine,
// in which case we don't care about game-pad support
return nullptr;
}
return aznew InputDeviceGamepadWindows(inputDevice, xinputModuleHandle);
}
////////////////////////////////////////////////////////////////////////////////////////////////
InputDeviceGamepadWindows::InputDeviceGamepadWindows(InputDeviceGamepad& inputDevice,
AZStd::shared_ptr<AZ::DynamicModuleHandle> xinputModuleHandle)
: InputDeviceGamepad::Implementation(inputDevice)
, m_xinputModuleHandle(xinputModuleHandle)
, m_rawGamepadState(GetDigitalButtonIdByBitMaskMap())
, m_isConnected(false)
, m_tryConnect(true)
{
AZ_Assert(m_xinputModuleHandle, "Creating instance of InputDeviceGamepadWindows with a null XInput handle.");
AZ_Assert(inputDevice.GetInputDeviceId().GetIndex() < InputDeviceGamepad::GetMaxSupportedGamepads(),
"Creating InputDeviceGamepadWindows with index %d that is greater than the max supported by xinput: %d",
inputDevice.GetInputDeviceId().GetIndex(), InputDeviceGamepad::GetMaxSupportedGamepads());
m_rawGamepadState.m_triggerMaximumValue = AnalogTriggerMaxValue;
m_rawGamepadState.m_triggerDeadZoneValue = AnalogTriggerDeadZone;
m_rawGamepadState.m_thumbStickMaximumValue = ThumbStickMaxValue;
m_rawGamepadState.m_thumbStickLeftDeadZone = ThumbStickLeftDeadZone;
m_rawGamepadState.m_thumbStickRightDeadZone = ThumbStickRightDeadZone;
RawInputNotificationBusWindows::Handler::BusConnect();
}
////////////////////////////////////////////////////////////////////////////////////////////////
InputDeviceGamepadWindows::~InputDeviceGamepadWindows()
{
RawInputNotificationBusWindows::Handler::BusDisconnect();
// This basically defeats the purpose of using a weak_ptr in the first place, but we must
// explicitly call reset on it before the application shuts down so that the shared count
// object (which was allocated by the system allocator) is deleted before global shutdown.
m_xinputModuleHandle.reset();
if (XInput::WeakHandleToDynamicModule.expired())
{
XInput::WeakHandleToDynamicModule.reset();
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
bool InputDeviceGamepadWindows::IsConnected() const
{
return m_isConnected;
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceGamepadWindows::SetVibration(float leftMotorSpeedNormalized,
float rightMotorSpeedNormalized)
{
if (m_isConnected)
{
XINPUT_VIBRATION vibration;
vibration.wLeftMotorSpeed = static_cast<WORD>(VibrationMaxValue * AZ::GetClamp(leftMotorSpeedNormalized, 0.0f, 1.0f));
vibration.wRightMotorSpeed = static_cast<WORD>(VibrationMaxValue * AZ::GetClamp(rightMotorSpeedNormalized, 0.0f, 1.0f));
XInput::SetStateFunctionPointer(GetInputDeviceIndex(), &vibration);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
bool InputDeviceGamepadWindows::GetPhysicalKeyOrButtonText(const InputChannelId& inputChannelId,
AZStd::string& o_keyOrButtonText) const
{
if (inputChannelId == InputDeviceGamepad::Button::Select)
{
o_keyOrButtonText = "Back";
return true;
}
return false;
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceGamepadWindows::TickInputDevice()
{
// Only process gamepad input while this thread's message queue has focus to
// keep the behaviour consistent with the mouse and keyboard implementations.
if (::GetFocus() == nullptr)
{
return;
}
// Calling XInputGetState every frame for unconnected gamepad devices is extremely slow, but
// calling XInputGetState every frame for a device that is already connected is much faster.
// To get around this, unless we're already connected we'll only call XInputGetState once at
// startup and once each time we're notified of new connections by a WM_DEVICECHANGE message.
if (!m_isConnected)
{
if (!m_tryConnect)
{
return;
}
m_tryConnect = false;
}
XINPUT_STATE newInputState;
ZeroMemory(&newInputState, sizeof(XINPUT_STATE));
const AZ::u32 deviceIndex = GetInputDeviceIndex();
const DWORD result = XInput::GetStateFunctionPointer(deviceIndex, &newInputState);
if (result == ERROR_SUCCESS)
{
if (!m_isConnected)
{
// The game-pad connected since the last call to this function
m_isConnected = true;
BroadcastInputDeviceConnectedEvent();
}
// Always update the input channels while the game-pad is connected
m_rawGamepadState.m_digitalButtonStates = newInputState.Gamepad.wButtons;
m_rawGamepadState.m_triggerButtonLState = static_cast<float>(newInputState.Gamepad.bLeftTrigger);
m_rawGamepadState.m_triggerButtonRState = static_cast<float>(newInputState.Gamepad.bRightTrigger);
m_rawGamepadState.m_thumbStickLeftXState = static_cast<float>(newInputState.Gamepad.sThumbLX);
m_rawGamepadState.m_thumbStickLeftYState = static_cast<float>(newInputState.Gamepad.sThumbLY);
m_rawGamepadState.m_thumbStickRightXState = static_cast<float>(newInputState.Gamepad.sThumbRX);
m_rawGamepadState.m_thumbStickRightYState = static_cast<float>(newInputState.Gamepad.sThumbRY);
ProcessRawGamepadState(m_rawGamepadState);
}
else if (m_isConnected)
{
// The game-pad disconnected since the last call to this function
m_isConnected = false;
m_rawGamepadState.Reset();
ResetInputChannelStates();
BroadcastInputDeviceDisconnectedEvent();
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceGamepadWindows::OnRawInputDeviceChangeEvent()
{
// Calling XInputGetState every frame for unconnected gamepad devices is extremely slow, but
// calling XInputGetState every frame for a device that is already connected is much faster.
// To get around this, unless we're already connected we'll only call XInputGetState once at
// startup and once each time we're notified of new connections by a WM_DEVICECHANGE message.
if (!m_isConnected)
{
m_tryConnect = true;
}
}
} // namespace AzFramework
@@ -0,0 +1,289 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <../Common/WinAPI/AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard_WinAPI.h>
#include <AzFramework/Input/Buses/Notifications/RawInputNotificationBus_Platform.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace
{
// These are scarcely documented, and do not seem to be publically accessible:
// - https://msdn.microsoft.com/en-us/library/windows/desktop/ms645546(v=vs.85).aspx
// - https://msdn.microsoft.com/en-us/library/ff543440.aspx
const USHORT RAW_INPUT_KEYBOARD_USAGE_PAGE = 0x01;
const USHORT RAW_INPUT_KEYBOARD_USAGE = 0x06;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace AzFramework
{
////////////////////////////////////////////////////////////////////////////////////////////////
//! Platform specific implementation for Windows keyboard input devices
class InputDeviceKeyboardWindows : public InputDeviceKeyboard::Implementation
, public RawInputNotificationBusWindows::Handler
{
////////////////////////////////////////////////////////////////////////////////////////////
//! Count of the number instances of this class that have been created
static int s_instanceCount;
public:
////////////////////////////////////////////////////////////////////////////////////////////
// Allocator
AZ_CLASS_ALLOCATOR(InputDeviceKeyboardWindows, AZ::SystemAllocator, 0);
////////////////////////////////////////////////////////////////////////////////////////////
//! Constructor
//! \param[in] inputDevice Reference to the input device being implemented
InputDeviceKeyboardWindows(InputDeviceKeyboard& inputDevice);
////////////////////////////////////////////////////////////////////////////////////////////
//! Destructor
~InputDeviceKeyboardWindows() 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 InputTextEntryRequests::VirtualKeyboardOptions& options) override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceKeyboard::Implementation::TextEntryStop
void TextEntryStop() override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceKeyboard::Implementation::TickInputDevice
void TickInputDevice() override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceKeyboard::Implementation::GetPhysicalKeyOrButtonText
void GetPhysicalKeyOrButtonText(const InputChannelId& inputChannelId,
AZStd::string& o_keyOrButtonText) const override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::RawInputNotificationsWindows::OnRawInputEvent
void OnRawInputEvent(const RAWINPUT& rawInput) override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::RawInputNotificationsWindows::OnRawInputCodeUnitUTF16Event
void OnRawInputCodeUnitUTF16Event(uint16_t codeUnitUTF16) override;
////////////////////////////////////////////////////////////////////////////////////////////
//! If a codeUnitUTF16 param passed to OnRawInputCodeUnitUTF16Event is part of a 2 code-unit
//! sequence (ie. it doesn't correspond directly to a single UTF16 code-point) we must store
//! the 'lead surrogate' so the subsequent 'trailing surrogate' can be correctly interpreted.
UTF16ToUTF8Converter m_UTF16ToUTF8Converter;
////////////////////////////////////////////////////////////////////////////////////////////
//! Cached map of Windows scan codes indexed by their corresponding input channel id
AZStd::unordered_map<InputChannelId, AZ::u32> m_scanCodesByInputChannelId;
////////////////////////////////////////////////////////////////////////////////////////////
//! Does the window attached to the input (main) thread's message queue have focus?
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms646294(v=vs.85).aspx
bool m_hasFocus = false;
////////////////////////////////////////////////////////////////////////////////////////////
//! Has text entry been started?
bool m_hasTextEntryStarted = false;
};
////////////////////////////////////////////////////////////////////////////////////////////////
InputDeviceKeyboard::Implementation* InputDeviceKeyboard::Implementation::Create(InputDeviceKeyboard& inputDevice)
{
return aznew InputDeviceKeyboardWindows(inputDevice);
}
////////////////////////////////////////////////////////////////////////////////////////////////
int InputDeviceKeyboardWindows::s_instanceCount = 0;
////////////////////////////////////////////////////////////////////////////////////////////////
InputDeviceKeyboardWindows::InputDeviceKeyboardWindows(InputDeviceKeyboard& inputDevice)
: InputDeviceKeyboard::Implementation(inputDevice)
, m_UTF16ToUTF8Converter()
, m_scanCodesByInputChannelId(ConstructScanCodeByInputChannelIdMap())
, m_hasFocus(false)
, m_hasTextEntryStarted(false)
{
if (s_instanceCount++ == 0)
{
// Register for raw keyboard input
RAWINPUTDEVICE rawInputDevice;
rawInputDevice.usUsagePage = RAW_INPUT_KEYBOARD_USAGE_PAGE;
rawInputDevice.usUsage = RAW_INPUT_KEYBOARD_USAGE;
rawInputDevice.dwFlags = 0;
rawInputDevice.hwndTarget = 0;
const BOOL result = RegisterRawInputDevices(&rawInputDevice, 1, sizeof(rawInputDevice));
AZ_Assert(result, "Failed to register raw input device: keyboard");
AZ_UNUSED(result);
}
RawInputNotificationBusWindows::Handler::BusConnect();
}
////////////////////////////////////////////////////////////////////////////////////////////////
InputDeviceKeyboardWindows::~InputDeviceKeyboardWindows()
{
RawInputNotificationBusWindows::Handler::BusDisconnect();
if (--s_instanceCount == 0)
{
// Deregister from raw keyboard input
RAWINPUTDEVICE rawInputDevice;
rawInputDevice.usUsagePage = RAW_INPUT_KEYBOARD_USAGE_PAGE;
rawInputDevice.usUsage = RAW_INPUT_KEYBOARD_USAGE;
rawInputDevice.dwFlags = RIDEV_REMOVE;
rawInputDevice.hwndTarget = 0;
const BOOL result = RegisterRawInputDevices(&rawInputDevice, 1, sizeof(rawInputDevice));
AZ_Assert(result, "Failed to deregister raw input device: keyboard");
AZ_UNUSED(result);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
bool InputDeviceKeyboardWindows::IsConnected() const
{
// If necessary, we can register raw input devices using RIDEV_DEVNOTIFY in order to receive
// WM_INPUT_DEVICE_CHANGE Windows messages in the WndProc function. These could then be sent
// using an EBus (RawInputNotificationBusWindows?) and used to keep track of connected state.
//
// Doing this would allow (in one respect force) us to distinguish between multiple physical
// devices of the same type. But given support for multiple keyboards is a fairly niche need
// we'll keep things simple (for now) and assume there's one (and only 1) keyboard connected
// at all times. In practice this means if multiple physical keyboards are connected we will
// process input from them all, but treat all the input as if it comes from the same device.
//
// If it becomes necessary to determine connected states of keyboard devices (and/or support
// distinguishing between multiple physical keyboards) we should implement this function and
// call BroadcastInputDeviceConnectedEvent/BroadcastInputDeviceDisconnectedEvent when needed.
//
// Note that doing so will require modifying how we create and manage keyboard input devices
// in InputSystemComponent/InputSystemComponentWin so we create multiple InputDeviceKeyboard
// instances (somehow associating each with a raw input device id), along with modifying the
// InputDeviceKeyboardWindows::OnRawInputEvent function to filter incoming events by raw id.
return true;
}
////////////////////////////////////////////////////////////////////////////////////////////////
bool InputDeviceKeyboardWindows::HasTextEntryStarted() const
{
return m_hasTextEntryStarted;
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceKeyboardWindows::TextEntryStart(const InputTextEntryRequests::VirtualKeyboardOptions&)
{
m_hasTextEntryStarted = true;
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceKeyboardWindows::TextEntryStop()
{
m_hasTextEntryStarted = false;
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceKeyboardWindows::TickInputDevice()
{
// The input event loop is pumped by the system on Windows so all raw input events for this
// frame have already been dispatched. But they are all queued until ProcessRawEventQueues
// is called below so that all raw input events are processed at the same time every frame.
const bool hadFocus = m_hasFocus;
m_hasFocus = ::GetFocus() != nullptr;
if (m_hasFocus)
{
// Process raw event queues once each frame while this thread's message queue has focus
ProcessRawEventQueues();
if (!hadFocus)
{
// If we just gained focus, reset state after processing any events that are queued so that we don't have stale state lying around
ResetInputChannelStates();
}
}
else if (hadFocus)
{
// The window attached to this thread's message queue no longer has focus, process any
// events that are queued, before resetting the state of all associated input channels.
ProcessRawEventQueues();
ResetInputChannelStates();
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceKeyboardWindows::GetPhysicalKeyOrButtonText(const InputChannelId& inputChannelId,
AZStd::string& o_keyOrButtonText) const
{
const auto& it = m_scanCodesByInputChannelId.find(inputChannelId);
if (it == m_scanCodesByInputChannelId.end())
{
return;
}
static const int bufferLength = 64;
WCHAR buffer[bufferLength];
const AZ::u32 scanCode = it->second;
LONG lParam = scanCode << 16;
const int stringLength = GetKeyNameTextW(lParam, buffer, bufferLength);
if (stringLength != 0)
{
// Convert UTF-16 to UTF-8
AZStd::to_string(o_keyOrButtonText, buffer, stringLength);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceKeyboardWindows::OnRawInputEvent(const RAWINPUT& rawInput)
{
if (rawInput.header.dwType != RIM_TYPEKEYBOARD || !::GetFocus())
{
return;
}
const RAWKEYBOARD& rawKeyboardData = rawInput.data.keyboard;
const AZ::u32 scanCode = rawKeyboardData.MakeCode;
const AZ::u32 virtualKeyCode = rawKeyboardData.VKey;
const bool hasExtendedKeyPrefix = ((rawKeyboardData.Flags & RI_KEY_E0) != 0);
const InputChannelId* channelId = GetInputChannelIdFromRawKeyEvent(scanCode,
virtualKeyCode,
hasExtendedKeyPrefix);
if (channelId)
{
const bool isKeyPressed = ((rawKeyboardData.Flags & RI_KEY_BREAK) == 0);
QueueRawKeyEvent(*channelId, isKeyPressed);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceKeyboardWindows::OnRawInputCodeUnitUTF16Event(uint16_t codeUnitUTF16)
{
const AZStd::string codePointUTF8 = m_UTF16ToUTF8Converter.FeedCodeUnitUTF16(codeUnitUTF16);
#if !defined(ALWAYS_DISPATCH_KEYBOARD_TEXT_INPUT)
if (!m_hasTextEntryStarted)
{
return;
}
#endif // !defined(ALWAYS_DISPATCH_KEYBOARD_TEXT_INPUT)
if (!codePointUTF8.empty())
{
QueueRawTextEvent(codePointUTF8);
}
}
} // namespace AzFramework
@@ -0,0 +1,456 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/PlatformIncl.h>
#include <AzFramework/Input/Devices/Mouse/InputDeviceMouse.h>
#include <AzFramework/Input/Buses/Notifications/RawInputNotificationBus_Platform.h>
#include <AzFramework/Input/Buses/Requests/InputSystemCursorRequestBus.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace
{
// These are scarcely documented, and do not seem to be publically accessible:
// - https://msdn.microsoft.com/en-us/library/windows/desktop/ms645546(v=vs.85).aspx
// - https://msdn.microsoft.com/en-us/library/ff543440.aspx
const USHORT RAW_INPUT_MOUSE_USAGE_PAGE = 0x01;
const USHORT RAW_INPUT_MOUSE_USAGE = 0x02;
////////////////////////////////////////////////////////////////////////////////////////////////
//! Get the focus window that should be used to clip and/or normalize the system cursor.
//! \return The HWND that should currently be considered the applictaion's focus window.
HWND GetSystemCursorFocusWindow()
{
void* systemCursorFocusWindow = nullptr;
AzFramework::InputSystemCursorConstraintRequestBus::BroadcastResult(
systemCursorFocusWindow,
&AzFramework::InputSystemCursorConstraintRequests::GetSystemCursorConstraintWindow);
return systemCursorFocusWindow ? static_cast<HWND>(systemCursorFocusWindow) : ::GetFocus();
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace AzFramework
{
////////////////////////////////////////////////////////////////////////////////////////////////
//! Platform specific implementation for Windows mouse input devices
class InputDeviceMouseWindows : public InputDeviceMouse::Implementation
, public RawInputNotificationBusWindows::Handler
{
////////////////////////////////////////////////////////////////////////////////////////////
//! Count of the number instances of this class that have been created
static int s_instanceCount;
public:
////////////////////////////////////////////////////////////////////////////////////////////
// Allocator
AZ_CLASS_ALLOCATOR(InputDeviceMouseWindows, AZ::SystemAllocator, 0);
////////////////////////////////////////////////////////////////////////////////////////////
//! Constructor
//! \param[in] inputDevice Reference to the input device being implemented
InputDeviceMouseWindows(InputDeviceMouse& inputDevice);
////////////////////////////////////////////////////////////////////////////////////////////
//! Destructor
~InputDeviceMouseWindows() override;
private:
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceMouse::Implementation::IsConnected
bool IsConnected() const override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceMouse::Implementation::SetSystemCursorState
void SetSystemCursorState(SystemCursorState systemCursorState) override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceMouse::Implementation::GetSystemCursorState
SystemCursorState GetSystemCursorState() const override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceMouse::Implementation::SetSystemCursorPositionNormalized
void SetSystemCursorPositionNormalized(AZ::Vector2 positionNormalized) override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceMouse::Implementation::GetSystemCursorPositionNormalized
AZ::Vector2 GetSystemCursorPositionNormalized() const override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceMouse::Implementation::TickInputDevice
void TickInputDevice() override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::RawInputNotificationsWindows::OnRawInputEvent
void OnRawInputEvent(const RAWINPUT& rawInput) override;
////////////////////////////////////////////////////////////////////////////////////////////
//! Refresh system cursor clipping constraint
void RefreshSystemCursorClippingConstraint();
////////////////////////////////////////////////////////////////////////////////////////////
//! Refresh system cursor viibility
void RefreshSystemCursorVisibility();
////////////////////////////////////////////////////////////////////////////////////////////
//! The current system cursor state
SystemCursorState m_systemCursorState;
////////////////////////////////////////////////////////////////////////////////////////////
//! Does the window attached to the input (main) thread's message queue have focus?
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms646294(v=vs.85).aspx
bool m_hasFocus;
////////////////////////////////////////////////////////////////////////////////////////////
//! The client rect of the window obtained the last time this input device was ticked.
RECT m_lastClientRect;
//! The flags sent with the last received MOUSE_MOVE_RELATIVE or MOUSE_MOVE_ABSOLUTE event.
USHORT m_lastMouseMoveEventFlags;
////////////////////////////////////////////////////////////////////////////////////////////
//! The last absolute mouse position, reported by a MOUSE_MOVE_ABSOLUTE raw input API event,
//! which will more than likely only ever be received when running a remote desktop session.
AZ::Vector2 m_lastMouseMoveEventAbsolutePosition;
};
////////////////////////////////////////////////////////////////////////////////////////////////
InputDeviceMouse::Implementation* InputDeviceMouse::Implementation::Create(InputDeviceMouse& inputDevice)
{
return aznew InputDeviceMouseWindows(inputDevice);
}
////////////////////////////////////////////////////////////////////////////////////////////////
int InputDeviceMouseWindows::s_instanceCount = 0;
////////////////////////////////////////////////////////////////////////////////////////////////
InputDeviceMouseWindows::InputDeviceMouseWindows(InputDeviceMouse& inputDevice)
: InputDeviceMouse::Implementation(inputDevice)
, m_systemCursorState(SystemCursorState::Unknown)
, m_hasFocus(false)
, m_lastMouseMoveEventFlags(0)
, m_lastMouseMoveEventAbsolutePosition()
{
memset(&m_lastClientRect, 0, sizeof(m_lastClientRect));
if (s_instanceCount++ == 0)
{
// Register for raw mouse input
RAWINPUTDEVICE rawInputDevice;
rawInputDevice.usUsagePage = RAW_INPUT_MOUSE_USAGE_PAGE;
rawInputDevice.usUsage = RAW_INPUT_MOUSE_USAGE;
rawInputDevice.dwFlags = 0;
rawInputDevice.hwndTarget = 0;
const BOOL result = RegisterRawInputDevices(&rawInputDevice, 1, sizeof(rawInputDevice));
AZ_Assert(result, "Failed to register raw input device: mouse");
AZ_UNUSED(result);
}
RawInputNotificationBusWindows::Handler::BusConnect();
}
////////////////////////////////////////////////////////////////////////////////////////////////
InputDeviceMouseWindows::~InputDeviceMouseWindows()
{
RawInputNotificationBusWindows::Handler::BusDisconnect();
// Cleanup system cursor visibility and constraint
SetSystemCursorState(SystemCursorState::Unknown);
if (--s_instanceCount == 0)
{
// Deregister from raw mouse input
RAWINPUTDEVICE rawInputDevice;
rawInputDevice.usUsagePage = RAW_INPUT_MOUSE_USAGE_PAGE;
rawInputDevice.usUsage = RAW_INPUT_MOUSE_USAGE;
rawInputDevice.dwFlags = RIDEV_REMOVE;
rawInputDevice.hwndTarget = 0;
const BOOL result = RegisterRawInputDevices(&rawInputDevice, 1, sizeof(rawInputDevice));
AZ_Assert(result, "Failed to deregister raw input device: mouse");
AZ_UNUSED(result);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
bool InputDeviceMouseWindows::IsConnected() const
{
// If necessary, we can register raw input devices using RIDEV_DEVNOTIFY in order to receive
// WM_INPUT_DEVICE_CHANGE Windows messages in the WndProc function. These could then be sent
// using an EBus (RawInputNotificationBusWindows?) and used to keep track of connected state.
//
// Doing this would allow (in one respect force) us to distinguish between multiple physical
// devices of the same type. But given that support for multiple mice is a fairly niche need
// we'll keep things simple (for now) and assume there is one (and only one) mouse connected
// at all times. In practice this means that if multiple physical mice are connected we will
// process input from them all, but treat all the input as if it comes from the same device.
//
// If it becomes necessary to determine the connected state of mouse devices (and/or support
// distinguishing between multiple physical mice), we should implement this function as well
// call BroadcastInputDeviceConnectedEvent/BroadcastInputDeviceDisconnectedEvent when needed.
//
// Note that doing so will require modifying how we create and manage mouse input devices in
// InputSystemComponent/InputSystemComponentWin in order to create multiple InputDeviceMouse
// instances (somehow associating each with a RID_DEVICE_INFO_MOUSE), and also modifying the
// InputDeviceMouseWindows::OnRawInputEvent function to filter incoming events by device id.
return true;
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceMouseWindows::SetSystemCursorState(SystemCursorState systemCursorState)
{
if (systemCursorState != m_systemCursorState)
{
m_systemCursorState = systemCursorState;
RefreshSystemCursorClippingConstraint();
RefreshSystemCursorVisibility();
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
SystemCursorState InputDeviceMouseWindows::GetSystemCursorState() const
{
return m_systemCursorState;
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceMouseWindows::SetSystemCursorPositionNormalized(AZ::Vector2 positionNormalized)
{
HWND focusWindow = GetSystemCursorFocusWindow();
if (!focusWindow)
{
return;
}
// Get the content (client) rect of the focus window
RECT clientRect;
::GetClientRect(focusWindow, &clientRect);
const float clientWidth = static_cast<float>(clientRect.right - clientRect.left);
const float clientHeight = static_cast<float>(clientRect.bottom - clientRect.top);
// De-normalize the position relative to the focus window, then transform to screen coords
POINT cursorPos;
cursorPos.x = static_cast<LONG>(positionNormalized.GetX() * clientWidth);
cursorPos.y = static_cast<LONG>(positionNormalized.GetY() * clientHeight);
::ClientToScreen(focusWindow, &cursorPos);
::SetCursorPos(cursorPos.x, cursorPos.y);
}
////////////////////////////////////////////////////////////////////////////////////////////////
AZ::Vector2 InputDeviceMouseWindows::GetSystemCursorPositionNormalized() const
{
HWND focusWindow = GetSystemCursorFocusWindow();
if (!focusWindow)
{
return AZ::Vector2::CreateZero();
}
// Get the position of the cursor relative to the focus window
POINT cursorPos;
::GetCursorPos(&cursorPos);
::ScreenToClient(focusWindow, &cursorPos);
// Get the content (client) rect of the focus window
RECT clientRect;
::GetClientRect(focusWindow, &clientRect);
// Normalize the cursor position relative to the content (client rect) fo the focus window
const float clientRectWidth = static_cast<float>(clientRect.right - clientRect.left);
const float clientRectHeight = static_cast<float>(clientRect.bottom - clientRect.top);
const float normalizedCursorPostionX = static_cast<float>(cursorPos.x) / clientRectWidth;
const float normalizedCursorPostionY = static_cast<float>(cursorPos.y) / clientRectHeight;
return AZ::Vector2(normalizedCursorPostionX, normalizedCursorPostionY);
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceMouseWindows::TickInputDevice()
{
// The input event loop is pumped by the system on Windows so all raw input events for this
// frame have already been dispatched. But they are all queued until ProcessRawEventQueues
// is called below so that all raw input events are processed at the same time every frame.
const bool hadFocus = m_hasFocus;
m_hasFocus = ::GetFocus() != nullptr;
if (m_hasFocus)
{
RECT clientRect;
HWND focusWindow = GetSystemCursorFocusWindow();
::GetClientRect(focusWindow, &clientRect);
::ClientToScreen(focusWindow, (LPPOINT)&clientRect.left); // Converts the top-left point
::ClientToScreen(focusWindow, (LPPOINT)&clientRect.right); // Converts the bottom-right point
if (!hadFocus ||
clientRect.top != m_lastClientRect.top ||
clientRect.left != m_lastClientRect.left ||
clientRect.right != m_lastClientRect.right ||
clientRect.bottom != m_lastClientRect.bottom)
{
// We have to refresh the system cursor clip rect each time the application gains
// focus, changes resolution, or transitions between fullscreen and windowed mode.
// This is in order to combat the cursor being unclipped by the system or another
// application which can happen as a result of the cursor being a shared resource.
RefreshSystemCursorClippingConstraint();
}
memcpy(&m_lastClientRect, &clientRect, sizeof(m_lastClientRect));
// Process raw event queues once each frame while this thread's message queue has focus
ProcessRawEventQueues();
}
else if (hadFocus)
{
// The window attached to this thread's message queue no longer has focus, process any
// events that are queued, before resetting the state of all associated input channels.
ProcessRawEventQueues();
ResetInputChannelStates();
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceMouseWindows::OnRawInputEvent(const RAWINPUT& rawInput)
{
if (rawInput.header.dwType != RIM_TYPEMOUSE || !::GetFocus())
{
return;
}
const RAWMOUSE& rawMouseData = rawInput.data.mouse;
const USHORT buttonFlags = rawMouseData.usButtonFlags;
// Left button
if (buttonFlags & RI_MOUSE_LEFT_BUTTON_DOWN)
{
QueueRawButtonEvent(InputDeviceMouse::Button::Left, true);
}
if (buttonFlags & RI_MOUSE_LEFT_BUTTON_UP)
{
QueueRawButtonEvent(InputDeviceMouse::Button::Left, false);
}
// Right button
if (buttonFlags & RI_MOUSE_RIGHT_BUTTON_DOWN)
{
QueueRawButtonEvent(InputDeviceMouse::Button::Right, true);
}
if (buttonFlags & RI_MOUSE_RIGHT_BUTTON_UP)
{
QueueRawButtonEvent(InputDeviceMouse::Button::Right, false);
}
// Middle button
if (buttonFlags & RI_MOUSE_MIDDLE_BUTTON_DOWN)
{
QueueRawButtonEvent(InputDeviceMouse::Button::Middle, true);
}
if (buttonFlags & RI_MOUSE_MIDDLE_BUTTON_UP)
{
QueueRawButtonEvent(InputDeviceMouse::Button::Middle, false);
}
// X1 button (deprecated)
if (buttonFlags & RI_MOUSE_BUTTON_4_DOWN)
{
QueueRawButtonEvent(InputDeviceMouse::Button::Other1, true);
}
if (buttonFlags & RI_MOUSE_BUTTON_4_UP)
{
QueueRawButtonEvent(InputDeviceMouse::Button::Other1, false);
}
// X2 button (deprecated)
if (buttonFlags & RI_MOUSE_BUTTON_5_DOWN)
{
QueueRawButtonEvent(InputDeviceMouse::Button::Other2, true);
}
if (buttonFlags & RI_MOUSE_BUTTON_5_UP)
{
QueueRawButtonEvent(InputDeviceMouse::Button::Other2, false);
}
// Scroll wheel
if (buttonFlags & RI_MOUSE_WHEEL)
{
QueueRawMovementEvent(InputDeviceMouse::Movement::Z, static_cast<short>(rawMouseData.usButtonData));
}
// Mouse movement
if (rawMouseData.usFlags == MOUSE_MOVE_RELATIVE)
{
QueueRawMovementEvent(InputDeviceMouse::Movement::X, static_cast<float>(rawMouseData.lLastX));
QueueRawMovementEvent(InputDeviceMouse::Movement::Y, static_cast<float>(rawMouseData.lLastY));
m_lastMouseMoveEventFlags = rawMouseData.usFlags;
}
else if (rawMouseData.usFlags & MOUSE_MOVE_ABSOLUTE)
{
const bool isVirtualDesktop = (rawMouseData.usFlags & MOUSE_VIRTUAL_DESKTOP) != 0;
const int screenWidth = GetSystemMetrics(isVirtualDesktop ? SM_CXVIRTUALSCREEN : SM_CXSCREEN);
const int screenHeight = GetSystemMetrics(isVirtualDesktop ? SM_CYVIRTUALSCREEN : SM_CYSCREEN);
const float absoluteX = (static_cast<float>(rawMouseData.lLastX) / static_cast<float>(USHRT_MAX)) * static_cast<float>(screenWidth);
const float absoluteY = (static_cast<float>(rawMouseData.lLastY) / static_cast<float>(USHRT_MAX)) * static_cast<float>(screenHeight);
if (m_lastMouseMoveEventFlags & MOUSE_MOVE_ABSOLUTE)
{
// Only calculate and send the delta if we have previously cached a valid position
const float deltaX = absoluteX - m_lastMouseMoveEventAbsolutePosition.GetX();
const float deltaY = absoluteY - m_lastMouseMoveEventAbsolutePosition.GetY();
QueueRawMovementEvent(InputDeviceMouse::Movement::X, deltaX);
QueueRawMovementEvent(InputDeviceMouse::Movement::Y, deltaY);
}
m_lastMouseMoveEventFlags = rawMouseData.usFlags;
m_lastMouseMoveEventAbsolutePosition.SetX(absoluteX);
m_lastMouseMoveEventAbsolutePosition.SetY(absoluteY);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceMouseWindows::RefreshSystemCursorClippingConstraint()
{
HWND focusWindow = GetSystemCursorFocusWindow();
if (!focusWindow)
{
return;
}
const bool shouldBeConstrained = (m_systemCursorState == SystemCursorState::ConstrainedAndHidden) ||
(m_systemCursorState == SystemCursorState::ConstrainedAndVisible);
if (!shouldBeConstrained)
{
// Unconstrain the cursor
::ClipCursor(NULL);
return;
}
// Constrain the cursor to the client (content) rect of the focus window
RECT clientRect;
::GetClientRect(focusWindow, &clientRect);
::ClientToScreen(focusWindow, (LPPOINT)&clientRect.left); // Converts the top-left point
::ClientToScreen(focusWindow, (LPPOINT)&clientRect.right); // Converts the bottom-right point
::ClipCursor(&clientRect);
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceMouseWindows::RefreshSystemCursorVisibility()
{
const bool shouldBeHidden = (m_systemCursorState == SystemCursorState::ConstrainedAndHidden) ||
(m_systemCursorState == SystemCursorState::UnconstrainedAndHidden);
// The Windows system ShowCursor function stores and returns an application specific display
// counter, and the cursor is displayed only when the display count is greater than or equal
// to zero: https://msdn.microsoft.com/en-us/library/windows/desktop/ms648396(v=vs.85).aspx
if (shouldBeHidden)
{
while (::ShowCursor(false) >= 0) {}
}
else
{
while (::ShowCursor(true) < 0) {}
}
}
} // namespace AzFramework
@@ -0,0 +1,14 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <Platform/Common/Default/AzFramework/Input/User/LocalUserId_Default.h>
@@ -0,0 +1,53 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzFramework/StringFunc/StringFunc.h>
#include <AzCore/IO/SystemFile.h>
#include <AzCore/PlatformIncl.h>
#include <AzCore/Utils/Utils.h>
namespace AzFramework
{
namespace Platform
{
AZStd::string GetPersistentName()
{
AZStd::string persistentName = "Lumberyard";
char procPath[AZ_MAX_PATH_LEN];
AZ::Utils::GetExecutablePathReturnType ret = AZ::Utils::GetExecutablePath(procPath, AZ_MAX_PATH_LEN);
if (ret.m_pathStored == AZ::Utils::ExecutablePathResult::Success)
{
AzFramework::StringFunc::Path::GetFileName(procPath, persistentName);
}
return persistentName;
}
//! On windows, if the neighborhood name was not provided we
//! will use the local computer name as the name since most of
//! the time the hub should be running on the local machine.
AZStd::string GetNeighborhoodName()
{
AZStd::string neighborhoodName;
char localhost[MAX_COMPUTERNAME_LENGTH + 1];
DWORD len = AZ_ARRAY_SIZE(localhost);
if (GetComputerName(localhost, &len))
{
neighborhoodName = localhost;
}
return neighborhoodName;
}
}
}
@@ -0,0 +1,387 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzFramework/Input/Buses/Notifications/RawInputNotificationBus_Windows.h>
#include <AzFramework/Windowing/NativeWindow.h>
#include <AzCore/PlatformIncl.h>
namespace AzFramework
{
class NativeWindowImpl_Win32 final
: public NativeWindow::Implementation
{
public:
AZ_CLASS_ALLOCATOR(NativeWindowImpl_Win32, AZ::SystemAllocator, 0);
NativeWindowImpl_Win32() = default;
~NativeWindowImpl_Win32() override;
// NativeWindow::Implementation overrides...
void InitWindow(const AZStd::string& title,
const WindowGeometry& geometry,
const WindowStyleMasks& styleMasks) override;
void Activate() override;
void Deactivate() override;
NativeWindowHandle GetWindowHandle() const override;
void SetWindowTitle(const AZStd::string& title) override;
void ResizeClientArea( WindowSize clientAreaSize ) override;
bool GetFullScreenState() const override;
void SetFullScreenState(bool fullScreenState) override;
bool CanToggleFullScreenState() const override { return true; }
private:
static DWORD ConvertToWin32WindowStyleMask(const WindowStyleMasks& styleMasks);
static LRESULT CALLBACK WindowCallback(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
static const char* s_defaultClassName;
void WindowSizeChanged(const uint32_t width, const uint32_t height);
void EnterBorderlessWindowFullScreen();
void ExitBorderlessWindowFullScreen();
HWND m_win32Handle = nullptr;
RECT m_windowRectToRestoreOnFullScreenExit; //!< The position and size of the window to restore when exiting full screen.
UINT m_windowStyleToRestoreOnFullScreenExit; //!< The style(s) of the window to restore when exiting full screen.
bool m_isInBorderlessWindowFullScreenState = false; //!< Was a borderless window used to enter full screen state?
};
const char* NativeWindowImpl_Win32::s_defaultClassName = "LumberyardWin32Class";
NativeWindow::Implementation* NativeWindow::Implementation::Create()
{
return aznew NativeWindowImpl_Win32();
}
NativeWindowImpl_Win32::~NativeWindowImpl_Win32()
{
DestroyWindow(m_win32Handle);
m_win32Handle = nullptr;
}
void NativeWindowImpl_Win32::InitWindow(const AZStd::string& title,
const WindowGeometry& geometry,
const WindowStyleMasks& styleMasks)
{
const HINSTANCE hInstance = GetModuleHandle(0);
// register window class if it does not exist
WNDCLASSEX windowClass;
if (GetClassInfoEx(hInstance, s_defaultClassName, &windowClass) == false)
{
windowClass.cbSize = sizeof(WNDCLASSEX);
windowClass.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
windowClass.lpfnWndProc = &NativeWindowImpl_Win32::WindowCallback;
windowClass.cbClsExtra = 0;
windowClass.cbWndExtra = 0;
windowClass.hInstance = hInstance;
windowClass.hIcon = LoadIcon(hInstance, IDI_APPLICATION);
windowClass.hCursor = LoadCursor(NULL, IDC_ARROW);
windowClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
windowClass.lpszMenuName = NULL;
windowClass.lpszClassName = s_defaultClassName;
windowClass.hIconSm = LoadIcon(hInstance, IDI_APPLICATION);
if (!RegisterClassEx(&windowClass))
{
AZ_Error("Windowing", false, "Failed to register Win32 window class with error: %d", GetLastError());
}
}
const DWORD windowStyle = ConvertToWin32WindowStyleMask(styleMasks);
const BOOL windowHasMenu = FALSE;
// Adjust the window size so that the geometry we were given is the size of the client area.
RECT windowRect =
{
static_cast<LONG>(geometry.m_posX),
static_cast<LONG>(geometry.m_posY),
static_cast<LONG>(geometry.m_posX + geometry.m_width),
static_cast<LONG>(geometry.m_posY + geometry.m_height)
};
AdjustWindowRect(&windowRect, windowStyle, windowHasMenu);
// These are to store the client sizes, which will be smaller or equal to the window size
m_width = geometry.m_width;
m_height = geometry.m_height;
// create main window
m_win32Handle = CreateWindow(
s_defaultClassName, title.c_str(),
windowStyle,
geometry.m_posX, geometry.m_posY, windowRect.right - windowRect.left, windowRect.bottom - windowRect.top,
NULL, NULL, hInstance, NULL);
if (m_win32Handle == nullptr)
{
AZ_Error("Windowing", false, "Failed to create Win32 window with error: %d", GetLastError());
}
else
{
SetWindowLongPtr(m_win32Handle, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this));
}
}
void NativeWindowImpl_Win32::Activate()
{
if (!m_activated)
{
m_activated = true;
// This will result in a WM_SIZE message which will send the OnWindowResized notification
ShowWindow(m_win32Handle, SW_SHOW);
UpdateWindow(m_win32Handle);
}
}
void NativeWindowImpl_Win32::Deactivate()
{
if (m_activated) // nothing to do if window was already deactivated
{
m_activated = false;
WindowNotificationBus::Event(m_win32Handle, &WindowNotificationBus::Events::OnWindowClosed);
ShowWindow(m_win32Handle, SW_HIDE);
UpdateWindow(m_win32Handle);
}
}
NativeWindowHandle NativeWindowImpl_Win32::GetWindowHandle() const
{
return m_win32Handle;
}
void NativeWindowImpl_Win32::SetWindowTitle(const AZStd::string& title)
{
SetWindowText(m_win32Handle, title.c_str());
}
DWORD NativeWindowImpl_Win32::ConvertToWin32WindowStyleMask(const WindowStyleMasks& styleMasks)
{
DWORD nativeMask = styleMasks.m_platformSpecificStyleMask;
const uint32_t mask = styleMasks.m_platformAgnosticStyleMask;
if (mask & WindowStyleMasks::WINDOW_STYLE_BORDERED) { nativeMask |= WS_BORDER; }
if (mask & WindowStyleMasks::WINDOW_STYLE_RESIZEABLE) { nativeMask |= WS_BORDER | WS_THICKFRAME; }
if (mask & WindowStyleMasks::WINDOW_STYLE_TITLED) { nativeMask |= WS_CAPTION; }
if (mask & WindowStyleMasks::WINDOW_STYLE_TITLED_MENU) { nativeMask |= WS_CAPTION | WS_SYSMENU; }
if (mask & WindowStyleMasks::WINDOW_STYLE_MAXIMIZE) { nativeMask |= WS_CAPTION | WS_SYSMENU | WS_MAXIMIZEBOX; }
if (mask & WindowStyleMasks::WINDOW_STYLE_MINIMIZE) { nativeMask |= WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX; }
const DWORD defaultMask = WS_OVERLAPPEDWINDOW;
return nativeMask ? nativeMask : defaultMask;
}
// Handles Win32 Window Event callbacks
LRESULT CALLBACK NativeWindowImpl_Win32::WindowCallback(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
NativeWindowImpl_Win32* nativeWindowImpl = reinterpret_cast<NativeWindowImpl_Win32*>(GetWindowLongPtr(hWnd, GWLP_USERDATA));
switch (message)
{
case WM_CLOSE:
{
nativeWindowImpl->Deactivate();
break;
}
case WM_SIZE:
{
const uint16_t newWidth = LOWORD(lParam);
const uint16_t newHeight = HIWORD(lParam);
nativeWindowImpl->WindowSizeChanged(static_cast<uint32_t>(newWidth), static_cast<uint32_t>(newHeight));
break;
}
case WM_INPUT:
{
UINT rawInputSize;
const UINT rawInputHeaderSize = sizeof(RAWINPUTHEADER);
GetRawInputData((HRAWINPUT)lParam, RID_INPUT, NULL, &rawInputSize, rawInputHeaderSize);
LPBYTE rawInputBytes = new BYTE[rawInputSize];
const UINT bytesCopied = GetRawInputData((HRAWINPUT)lParam, RID_INPUT, rawInputBytes, &rawInputSize, rawInputHeaderSize);
RAWINPUT* rawInput = (RAWINPUT*)rawInputBytes;
AzFramework::RawInputNotificationBusWindows::Broadcast(
&AzFramework::RawInputNotificationBusWindows::Events::OnRawInputEvent, *rawInput);
break;
}
case WM_CHAR:
{
const unsigned short codeUnitUTF16 = static_cast<unsigned short>(wParam);
AzFramework::RawInputNotificationBusWindows::Broadcast(&AzFramework::RawInputNotificationsWindows::OnRawInputCodeUnitUTF16Event, codeUnitUTF16);
break;
}
default:
return DefWindowProc(hWnd, message, wParam, lParam);
break;
}
return 0;
}
void NativeWindowImpl_Win32::WindowSizeChanged(const uint32_t width, const uint32_t height)
{
if (m_width != width || m_height != height)
{
m_width = width;
m_height = height;
if (m_activated)
{
WindowNotificationBus::Event(m_win32Handle, &WindowNotificationBus::Events::OnWindowResized, width, height);
}
}
}
void NativeWindowImpl_Win32::ResizeClientArea(WindowSize clientAreaSize)
{
RECT rect = {};
GetClientRect(m_win32Handle, &rect);
rect.right = rect.left + clientAreaSize.m_width;
rect.bottom = rect.top + clientAreaSize.m_height;
AdjustWindowRect(&rect, WS_OVERLAPPEDWINDOW, false);
SetWindowPos(m_win32Handle, HWND_TOP, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, SWP_NOMOVE);
}
bool NativeWindowImpl_Win32::GetFullScreenState() const
{
if (m_isInBorderlessWindowFullScreenState)
{
return true;
}
bool isExclusiveFullScreenPreferred = false;
ExclusiveFullScreenRequestBus::EventResult(isExclusiveFullScreenPreferred,
m_win32Handle,
&ExclusiveFullScreenRequests::IsExclusiveFullScreenPreferred);
if (isExclusiveFullScreenPreferred)
{
// The renderer has assumed responsibility for maintaining the full screen state of this NativeWindow.
// This should only happen if tearing is not supported when using DX12 (see SwapChain::InitInternal).
bool exclusiveFullScreenState = false;
ExclusiveFullScreenRequestBus::EventResult(exclusiveFullScreenState,
m_win32Handle,
&ExclusiveFullScreenRequests::GetExclusiveFullScreenState);
return exclusiveFullScreenState;
}
return m_isInBorderlessWindowFullScreenState;
}
void NativeWindowImpl_Win32::SetFullScreenState(bool fullScreenState)
{
bool isExclusiveFullScreenPreferred = false;
ExclusiveFullScreenRequestBus::EventResult(isExclusiveFullScreenPreferred,
m_win32Handle,
&ExclusiveFullScreenRequests::IsExclusiveFullScreenPreferred);
if (isExclusiveFullScreenPreferred)
{
// The renderer has assumed responsibility for transitioning the full screen state of this NativeWindow.
// This should only happen if tearing is not supported when using DX12 (see SwapChain::InitInternal).
if (m_isInBorderlessWindowFullScreenState)
{
ExitBorderlessWindowFullScreen();
}
bool wasExclusiveFullScreenStateSet = false;
ExclusiveFullScreenRequestBus::EventResult(wasExclusiveFullScreenStateSet,
m_win32Handle,
&ExclusiveFullScreenRequests::SetExclusiveFullScreenState,
fullScreenState);
AZ_Warning("NativeWindowImpl_Win32::SetFullScreenState", wasExclusiveFullScreenStateSet,
"Could not set full screen state using ExclusiveFullScreenRequests::SetExclusiveFullScreenState.");
return;
}
if (fullScreenState)
{
EnterBorderlessWindowFullScreen();
}
else
{
ExitBorderlessWindowFullScreen();
}
}
void NativeWindowImpl_Win32::EnterBorderlessWindowFullScreen()
{
if (m_isInBorderlessWindowFullScreenState)
{
return;
}
// Get the monitor on which the window is currently displayed.
HMONITOR monitor = MonitorFromWindow(m_win32Handle, MONITOR_DEFAULTTONEAREST);
if (!monitor)
{
AZ_Warning("NativeWindowImpl_Win32::EnterBorderlessWindowFullScreen", false,
"Could not find any monitor.");
return;
}
// Get the dimensions of the display device on which the window is currently displayed.
MONITORINFO monitorInfo;
monitorInfo.cbSize = sizeof(MONITORINFO);
const BOOL success = monitor ? GetMonitorInfo(monitor, &monitorInfo) : FALSE;
if (!success)
{
AZ_Warning("NativeWindowImpl_Win32::SetFullScreenState", false,
"Could not get monitor info.");
return;
}
// Store the current window rect and style so we can restore them when exiting full screen.
GetWindowRect(m_win32Handle, &m_windowRectToRestoreOnFullScreenExit);
const UINT currentWindowStyle = GetWindowLong(m_win32Handle, GWL_STYLE);
m_windowStyleToRestoreOnFullScreenExit = currentWindowStyle;
// Style, resize, and position the window such that it fills the entire screen.
const RECT fullScreenWindowRect = monitorInfo.rcMonitor;
const UINT fullScreenWindowStyle = currentWindowStyle & ~(WS_CAPTION | WS_MAXIMIZEBOX | WS_MINIMIZEBOX | WS_SYSMENU | WS_THICKFRAME);
SetWindowLong(m_win32Handle, GWL_STYLE, fullScreenWindowStyle);
SetWindowPos(m_win32Handle,
HWND_TOPMOST,
fullScreenWindowRect.left,
fullScreenWindowRect.top,
fullScreenWindowRect.right,
fullScreenWindowRect.bottom,
SWP_FRAMECHANGED | SWP_NOACTIVATE);
ShowWindow(m_win32Handle, SW_MAXIMIZE);
m_isInBorderlessWindowFullScreenState = true;
}
void NativeWindowImpl_Win32::ExitBorderlessWindowFullScreen()
{
if (!m_isInBorderlessWindowFullScreenState)
{
return;
}
// Restore the style, size, and position of the window.
SetWindowLong(m_win32Handle, GWL_STYLE, m_windowStyleToRestoreOnFullScreenExit);
SetWindowPos(m_win32Handle,
HWND_NOTOPMOST,
m_windowRectToRestoreOnFullScreenExit.left,
m_windowRectToRestoreOnFullScreenExit.top,
m_windowRectToRestoreOnFullScreenExit.right - m_windowRectToRestoreOnFullScreenExit.left,
m_windowRectToRestoreOnFullScreenExit.bottom - m_windowRectToRestoreOnFullScreenExit.top,
SWP_FRAMECHANGED | SWP_NOACTIVATE);
ShowWindow(m_win32Handle, SW_NORMAL);
m_isInBorderlessWindowFullScreenState = false;
}
} // namespace AzFramework
@@ -0,0 +1,11 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
@@ -0,0 +1,38 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
set(FILES
AzFramework/AzFramework_Traits_Platform.h
AzFramework/AzFramework_Traits_Windows.h
AzFramework/API/ApplicationAPI_Platform.h
AzFramework/API/ApplicationAPI_Windows.h
AzFramework/Application/Application_Windows.cpp
AzFramework/Asset/AssetSystemComponentHelper_Windows.cpp
../Common/WinAPI/AzFramework/IO/LocalFileIO_WinAPI.cpp
AzFramework/IO/LocalFileIO_Windows.cpp
../Common/WinAPI/AzFramework/Network/AssetProcessorConnection_WinAPI.cpp
../Common/Unimplemented/AzFramework/StreamingInstall/StreamingInstall_Unimplemented.cpp
AzFramework/TargetManagement/TargetManagementComponent_Windows.cpp
AzFramework/Windowing/NativeWindow_Windows.cpp
AzFramework/Input/Buses/Notifications/RawInputNotificationBus_Platform.h
AzFramework/Input/Buses/Notifications/RawInputNotificationBus_Windows.h
AzFramework/Input/Devices/Gamepad/InputDeviceGamepad_Windows.cpp
AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard_Windows.cpp
../Common/WinAPI/AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard_WinAPI.h
../Common/Unimplemented/AzFramework/Input/Devices/Motion/InputDeviceMotion_Unimplemented.cpp
AzFramework/Input/Devices/Mouse/InputDeviceMouse_Windows.cpp
../Common/Unimplemented/AzFramework/Input/Devices/Touch/InputDeviceTouch_Unimplemented.cpp
AzFramework/Input/User/LocalUserId_Platform.h
../Common/Default/AzFramework/Input/User/LocalUserId_Default.h
../Common/Unimplemented/AzFramework/Input/Devices/VirtualKeyboard/InputDeviceVirtualKeyboard_Unimplemented.cpp
AzFramework/Archive/ArchiveVars_Platform.h
AzFramework/Archive/ArchiveVars_Windows.h
)