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,78 @@
/*
* 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>
#include <AzCore/std/string/string.h>
namespace AzFramework
{
class AndroidLifecycleEvents
: public AZ::EBusTraits
{
public:
// Bus Configuration
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
virtual ~AndroidLifecycleEvents() {}
using Bus = AZ::EBus<AndroidLifecycleEvents>;
virtual void OnLostFocus() {} // Constrain
virtual void OnGainedFocus() {} // Unconstrain
// Android can also generate onStop/onRestart events which (at first glance) would appear to match better with our
// suspend/resume events. However there is no guarantee of either these methods being called, and the behavior of
// onPause/onResume more closely matches that of suspend/resume on our other platforms.
virtual void OnPause() {} // Suspend
virtual void OnResume() {} // Resume
virtual void OnDestroy() {} // Terminate
virtual void OnLowMemory() {} // Low memory
virtual void OnWindowInit() {} // Application window was created
virtual void OnWindowDestroy() {} // Application window is going to be destroyed
virtual void OnWindowRedrawNeeded() {} // Application window needs to be redrawn. This is called after a window resize occurs as well. So, we can(reliably) use this for handling orientation changes.
};
class AndroidEventDispatcher
{
public:
virtual ~AndroidEventDispatcher() = default;
virtual void PumpAllEvents() = 0;
virtual void PumpEventLoopOnce() = 0;
};
class AndroidAppRequests
: public AZ::EBusTraits
{
public:
using Bus = AZ::EBus<AndroidAppRequests>;
// Bus Configuration
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
virtual ~AndroidAppRequests() {}
//! Sets the Android event dispatcher required to pumping the event loop
virtual void SetEventDispatcher(AndroidEventDispatcher* eventDispatcher) = 0;
//! Requests permissions at runtime
virtual bool RequestPermission(const AZStd::string& permission, const AZStd::string& rationale) = 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 <AzFramework/API/ApplicationAPI_Android.h>
@@ -0,0 +1,266 @@
/*
* 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/API/ApplicationAPI_Platform.h>
#include <AzFramework/Application/Application.h>
#include <AzFramework/Input/Buses/Notifications/RawInputNotificationBus_Platform.h>
#include <AzCore/Android/AndroidEnv.h>
#include <AzCore/Android/JNI/Object.h>
#include <AzCore/Android/JNI/scoped_ref.h>
#include <AzCore/Android/Utils.h>
#include <AzCore/std/parallel/atomic.h>
#include <AzCore/std/parallel/condition_variable.h>
#include <android/input.h>
#include <android/keycodes.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace AzFramework
{
namespace
{
class PermissionRequestResultNotification
: public AZ::EBusTraits
{
public:
using Bus = AZ::EBus<PermissionRequestResultNotification>;
//////////////////////////////////////////////////////////////////////////
// EBusTraits overrides
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
virtual ~PermissionRequestResultNotification() = default;
virtual void OnRequestPermissionsResult(bool granted) { AZ_UNUSED(granted); };
};
void JNI_OnRequestPermissionsResult(JNIEnv* env, jobject obj, bool granted)
{
AzFramework::PermissionRequestResultNotification::Bus::Broadcast(&AzFramework::PermissionRequestResultNotification::OnRequestPermissionsResult, granted);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
class ApplicationAndroid
: public Application::Implementation
, public AndroidLifecycleEvents::Bus::Handler
, public AndroidAppRequests::Bus::Handler
, public PermissionRequestResultNotification::Bus::Handler
{
public:
////////////////////////////////////////////////////////////////////////////////////////////
AZ_CLASS_ALLOCATOR(ApplicationAndroid, AZ::SystemAllocator, 0);
ApplicationAndroid();
~ApplicationAndroid() override;
////////////////////////////////////////////////////////////////////////////////////////////
// AndroidAppRequests
void SetEventDispatcher(AndroidEventDispatcher* eventDispatcher) override;
bool RequestPermission(const AZStd::string& permission, const AZStd::string& rationale) override;
////////////////////////////////////////////////////////////////////////////////////////////
// AndroidLifecycleEvents
void OnLostFocus() override;
void OnGainedFocus() override;
void OnPause() override;
void OnResume() override;
void OnDestroy() override;
void OnLowMemory() override;
void OnWindowInit() override;
void OnWindowDestroy() override;
void OnWindowRedrawNeeded() override;
////////////////////////////////////////////////////////////////////////////////////////////
// PermissionRequestResultNotification
void OnRequestPermissionsResult(bool granted) override;
////////////////////////////////////////////////////////////////////////////////////////////
// Application::Implementation
void PumpSystemEventLoopOnce() override;
void PumpSystemEventLoopUntilEmpty() override;
private:
AndroidEventDispatcher* m_eventDispatcher;
ApplicationLifecycleEvents::Event m_lastEvent;
AZStd::atomic<bool> m_requestResponseReceived;
AZStd::unique_ptr<AZ::Android::JNI::Object> m_lumberyardActivity;
AZStd::condition_variable m_conditionVar;
AZStd::mutex m_mutex;
bool m_permissionGranted;
};
////////////////////////////////////////////////////////////////////////////////////////////////
Application::Implementation* Application::Implementation::Create()
{
return aznew ApplicationAndroid();
}
////////////////////////////////////////////////////////////////////////////////////////////////
ApplicationAndroid::ApplicationAndroid()
: m_eventDispatcher(nullptr)
, m_lastEvent(ApplicationLifecycleEvents::Event::None)
{
m_lumberyardActivity.reset(aznew AZ::Android::JNI::Object(AZ::Android::Utils::GetActivityClassRef(), AZ::Android::Utils::GetActivityRef()));
m_lumberyardActivity->RegisterNativeMethods(
{ { "nativeOnRequestPermissionsResult", "(Z)V", (void*)JNI_OnRequestPermissionsResult } }
);
m_lumberyardActivity->RegisterMethod("RequestPermission", "(Ljava/lang/String;Ljava/lang/String;)V");
AndroidLifecycleEvents::Bus::Handler::BusConnect();
AndroidAppRequests::Bus::Handler::BusConnect();
PermissionRequestResultNotification::Bus::Handler::BusConnect();
}
////////////////////////////////////////////////////////////////////////////////////////////////
ApplicationAndroid::~ApplicationAndroid()
{
m_lumberyardActivity.reset();
PermissionRequestResultNotification::Bus::Handler::BusDisconnect();
AndroidAppRequests::Bus::Handler::BusDisconnect();
AndroidLifecycleEvents::Bus::Handler::BusDisconnect();
m_conditionVar.notify_all();
}
////////////////////////////////////////////////////////////////////////////////////////////////
void ApplicationAndroid::SetEventDispatcher(AndroidEventDispatcher* eventDispatcher)
{
AZ_Assert(!m_eventDispatcher, "Duplicate call to setting the Android event dispatcher!");
m_eventDispatcher = eventDispatcher;
}
////////////////////////////////////////////////////////////////////////////////////////////////
bool ApplicationAndroid::RequestPermission(const AZStd::string& permission, const AZStd::string& rationale)
{
AZ::Android::JNI::scoped_ref<jstring> permissionString(AZ::Android::JNI::ConvertStringToJstring(permission));
AZ::Android::JNI::scoped_ref<jstring> permissionRationaleString(AZ::Android::JNI::ConvertStringToJstring(rationale));
m_requestResponseReceived = false;
m_permissionGranted = false;
m_lumberyardActivity->InvokeVoidMethod("RequestPermission", permissionString.get(), permissionRationaleString.get());
bool looperExistsForThread = (ALooper_forThread() != nullptr);
// Make sure a looper exists for thread before pumping events.
// For threads that are not the main thread, we just block and
// the events will be pumped by the main thread and unblock this
// thread when the user responds.
if (looperExistsForThread)
{
while (!m_requestResponseReceived.load())
{
PumpSystemEventLoopOnce();
}
}
else
{
AZStd::unique_lock<AZStd::mutex> lock(m_mutex);
m_conditionVar.wait(lock, [&] { return m_requestResponseReceived.load(); });
}
return m_permissionGranted;
}
////////////////////////////////////////////////////////////////////////////////////////////////
void ApplicationAndroid::OnLostFocus()
{
EBUS_EVENT(ApplicationLifecycleEvents::Bus, OnApplicationConstrained, m_lastEvent);
m_lastEvent = ApplicationLifecycleEvents::Event::Constrain;
}
////////////////////////////////////////////////////////////////////////////////////////////////
void ApplicationAndroid::OnGainedFocus()
{
EBUS_EVENT(ApplicationLifecycleEvents::Bus, OnApplicationUnconstrained, m_lastEvent);
m_lastEvent = ApplicationLifecycleEvents::Event::Unconstrain;
}
////////////////////////////////////////////////////////////////////////////////////////////////
void ApplicationAndroid::OnPause()
{
EBUS_EVENT(ApplicationLifecycleEvents::Bus, OnApplicationSuspended, m_lastEvent);
m_lastEvent = ApplicationLifecycleEvents::Event::Suspend;
}
////////////////////////////////////////////////////////////////////////////////////////////////
void ApplicationAndroid::OnResume()
{
EBUS_EVENT(ApplicationLifecycleEvents::Bus, OnApplicationResumed, m_lastEvent);
m_lastEvent = ApplicationLifecycleEvents::Event::Resume;
}
////////////////////////////////////////////////////////////////////////////////////////////////
void ApplicationAndroid::OnDestroy()
{
EBUS_EVENT(ApplicationLifecycleEvents::Bus, OnMobileApplicationWillTerminate);
}
////////////////////////////////////////////////////////////////////////////////////////////////
void ApplicationAndroid::OnLowMemory()
{
EBUS_EVENT(ApplicationLifecycleEvents::Bus, OnMobileApplicationLowMemoryWarning);
}
////////////////////////////////////////////////////////////////////////////////////////////////
void ApplicationAndroid::OnWindowInit()
{
EBUS_EVENT(ApplicationLifecycleEvents::Bus, OnApplicationWindowCreated);
}
////////////////////////////////////////////////////////////////////////////////////////////////
void ApplicationAndroid::OnWindowDestroy()
{
EBUS_EVENT(ApplicationLifecycleEvents::Bus, OnApplicationWindowDestroy);
}
////////////////////////////////////////////////////////////////////////////////////////////////
void ApplicationAndroid::OnWindowRedrawNeeded()
{
EBUS_EVENT(ApplicationLifecycleEvents::Bus, OnApplicationWindowRedrawNeeded);
}
////////////////////////////////////////////////////////////////////////////////////////////////
void ApplicationAndroid::OnRequestPermissionsResult(bool granted)
{
m_permissionGranted = granted;
m_requestResponseReceived = true;
m_conditionVar.notify_all();
}
////////////////////////////////////////////////////////////////////////////////////////////////
void ApplicationAndroid::PumpSystemEventLoopOnce()
{
AZ_ErrorOnce("ApplicationAndroid", m_eventDispatcher, "The Android event dispatcher is not valid, unable to pump the event loop properly.");
if (m_eventDispatcher)
{
m_eventDispatcher->PumpEventLoopOnce();
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
void ApplicationAndroid::PumpSystemEventLoopUntilEmpty()
{
AZ_ErrorOnce("ApplicationAndroid", m_eventDispatcher, "The Android event dispatcher is not valid, unable to pump the event loop properly.");
if (m_eventDispatcher)
{
m_eventDispatcher->PumpAllEvents();
}
}
} // namespace AzFramework
@@ -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,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_Android.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 (true)
#define AZ_TRAIT_AZFRAMEWORK_BOOTSTRAP_CFG_CURRENT_PLATFORM "android"
#define AZ_TRAIT_AZFRAMEWORK_USE_C11_LOCALTIME_S 0
#define AZ_TRAIT_AZFRAMEWORK_USE_ERRNO_T_TYPEDEF 1
#define AZ_TRAIT_AZFRAMEWORK_USE_POSIX_LOCALTIME_R 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_Android.h>
@@ -0,0 +1,256 @@
/*
* 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 <dirent.h>
#include <sys/stat.h>
#include <fstream>
#include <AzFramework/IO/LocalFileIO.h>
#include <AzCore/Android/APKFileHandler.h>
#include <AzCore/Android/Utils.h>
#include <AzCore/IO/IOUtils.h>
#include <AzCore/IO/SystemFile.h>
#include <AzCore/std/functional.h>
#include <android/api-level.h>
#if __ANDROID_API__ == 19
// The following were apparently introduced in API 21, however in earlier versions of the
// platform specific headers they were defines. In the move to unified headers, the following
// defines were removed from stat.h
#ifndef stat64
#define stat64 stat
#endif
#ifndef fstat64
#define fstat64 fstat
#endif
#ifndef lstat64
#define lstat64 lstat
#endif
#endif // __ANDROID_API__ == 19
namespace AZ
{
namespace IO
{
bool LocalFileIO::IsDirectory(const char* filePath)
{
ANDROID_IO_PROFILE_SECTION_ARGS("IsDir:%s", filePath);
char resolvedPath[AZ_MAX_PATH_LEN];
ResolvePath(filePath, resolvedPath, AZ_MAX_PATH_LEN);
if (AZ::Android::Utils::IsApkPath(resolvedPath))
{
return AZ::Android::APKFileHandler::IsDirectory(AZ::Android::Utils::StripApkPrefix(resolvedPath));
}
struct stat result;
if (stat(resolvedPath, &result) == 0)
{
return S_ISDIR(result.st_mode);
}
return false;
}
Result LocalFileIO::Copy(const char* sourceFilePath, const char* destinationFilePath)
{
char resolvedSourcePath[AZ_MAX_PATH_LEN];
char resolvedDestPath[AZ_MAX_PATH_LEN];
ResolvePath(sourceFilePath, resolvedSourcePath, AZ_MAX_PATH_LEN);
ResolvePath(destinationFilePath, resolvedDestPath, AZ_MAX_PATH_LEN);
if (AZ::Android::Utils::IsApkPath(sourceFilePath) || AZ::Android::Utils::IsApkPath(destinationFilePath))
{
return ResultCode::Error; //copy from APK still to be implemented (and of course you can't copy to an APK)
}
// note: Android, without root, has no reliable way to update modtimes
// on files on internal storage - this includes "emulated" SDCARD storage
// that actually resides on internal, and thus we can't depend on modtimes.
{
std::ifstream sourceFile(resolvedSourcePath, std::ios::binary);
if (sourceFile.fail())
{
return ResultCode::Error;
}
std::ofstream destFile(resolvedDestPath, std::ios::binary);
if (destFile.fail())
{
return ResultCode::Error;
}
destFile << sourceFile.rdbuf();
}
return ResultCode::Success;
}
Result LocalFileIO::FindFiles(const char* filePath, const char* filter, LocalFileIO::FindFilesCallbackType callback)
{
ANDROID_IO_PROFILE_SECTION_ARGS("FindFiles:%s", filePath);
char resolvedPath[AZ_MAX_PATH_LEN];
ResolvePath(filePath, resolvedPath, AZ_MAX_PATH_LEN);
AZ::OSString pathWithoutSlash = RemoveTrailingSlash(resolvedPath);
bool isInAPK = AZ::Android::Utils::IsApkPath(pathWithoutSlash.c_str());
if (isInAPK)
{
AZ::OSString strippedPath = AZ::Android::Utils::StripApkPrefix(pathWithoutSlash.c_str());
char tempBuffer[AZ_MAX_PATH_LEN] = {0};
AZ::Android::APKFileHandler::ParseDirectory(strippedPath.c_str(), [&](const char* name)
{
AZStd::string_view filenameView = name;
// Skip over the current and parent directory paths
if (filenameView != "." && filenameView != ".." && NameMatchesFilter(name, filter))
{
AZ::OSString foundFilePath = CheckForTrailingSlash(resolvedPath);
foundFilePath += name;
// if aliased, de-alias!
azstrcpy(tempBuffer, AZ_MAX_PATH_LEN, foundFilePath.c_str());
ConvertToAlias(tempBuffer, AZ_MAX_PATH_LEN);
if (!callback(tempBuffer))
{
return false;
}
}
return true;
});
}
else
{
DIR* dir = opendir(pathWithoutSlash.c_str());
if (dir != nullptr)
{
// because the absolute path might actually be SHORTER than the alias ("c:/r/dev" -> "@devroot@"), we need to
// use a static buffer here.
char tempBuffer[AZ_MAX_PATH_LEN];
// clear the errno state so we can distinguish between errors and end of stream
errno = 0;
struct dirent* entry = readdir(dir);
// List all the other files in the directory.
while (entry != nullptr)
{
AZStd::string_view filenameView = entry->d_name;
// Skip over the current and parent directory paths
if (filenameView != "." && filenameView != ".." && NameMatchesFilter(entry->d_name, filter))
{
AZ::OSString foundFilePath = CheckForTrailingSlash(resolvedPath);
foundFilePath += entry->d_name;
// if aliased, de-alias!
azstrcpy(tempBuffer, AZ_MAX_PATH_LEN, foundFilePath.c_str());
ConvertToAlias(tempBuffer, AZ_MAX_PATH_LEN);
if (!callback(tempBuffer))
{
break;
}
}
entry = readdir(dir);
}
if (errno != 0)
{
closedir(dir);
return ResultCode::Error;
}
closedir(dir);
return ResultCode::Success;
}
else
{
return ResultCode::Error;
}
}
return ResultCode::Success;
}
Result LocalFileIO::CreatePath(const char* filePath)
{
char resolvedPath[AZ_MAX_PATH_LEN];
ResolvePath(filePath, resolvedPath, AZ_MAX_PATH_LEN);
if (AZ::Android::Utils::IsApkPath(resolvedPath))
{
return ResultCode::Error; //you can't write to the APK
}
// create all paths up to that directory.
// its not an error if the path exists.
if ((Exists(resolvedPath)) && (!IsDirectory(resolvedPath)))
{
return ResultCode::Error; // that path exists, but is not a directory.
}
// make directories from bottom to top.
AZ::OSString pathBuffer;
size_t pathLength = strlen(resolvedPath);
pathBuffer.reserve(pathLength);
for (size_t pathPos = 0; pathPos < pathLength; ++pathPos)
{
if ((resolvedPath[pathPos] == '\\') || (resolvedPath[pathPos] == '/'))
{
if (pathPos > 0)
{
mkdir(pathBuffer.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
if (!IsDirectory(pathBuffer.c_str()))
{
return ResultCode::Error;
}
}
}
pathBuffer.push_back(resolvedPath[pathPos]);
}
mkdir(pathBuffer.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
return IsDirectory(resolvedPath) ? ResultCode::Success : ResultCode::Error;
}
bool LocalFileIO::IsAbsolutePath(const char* path) const
{
return path && path[0] == '/';
}
bool LocalFileIO::ConvertToAbsolutePath(const char* path, char* absolutePath, AZ::u64 maxLength) const
{
if (AZ::Android::Utils::IsApkPath(path))
{
azstrncpy(absolutePath, maxLength, path, maxLength);
return true;
}
AZ_Assert(maxLength >= AZ_MAX_PATH_LEN, "Path length is larger than AZ_MAX_PATH_LEN");
if (!IsAbsolutePath(path))
{
// note that realpath fails if the path does not exist and actually changes the return value
// to be the actual place that FAILED, which we don't want.
// if we fail, we'd prefer to fall through and at least use the original path.
const char* result = realpath(path, absolutePath);
if (result)
{
return true;
}
}
azstrcpy(absolutePath, maxLength, path);
return IsAbsolutePath(absolutePath);
}
} // namespace IO
}//namespace AZ
@@ -0,0 +1,66 @@
/*
* 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>
struct AInputEvent;
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace AzFramework
{
////////////////////////////////////////////////////////////////////////////////////////////////
//! EBus interface used to listen for raw android input as broadcast by the system. Applications
//! that want android events to be processed by the AzFramework input system must broadcast all
//! input events received by the native input handle, which is the lowest level we can get input.
//!
//! It's possible to receive multiple events per index (finger) per frame, and it is likely that
//! android input events will not be dispatched from the main thread, so care should be taken to
//! ensure thread safety when implementing event handlers that connect to this android event bus.
//!
//! This EBus is intended primarily for the AzFramework input system to process raw input 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 android events.
class RawInputNotificationsAndroid : 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 ~RawInputNotificationsAndroid() = default;
////////////////////////////////////////////////////////////////////////////////////////////
//! Process raw input events (assumed to be dispatched on any thread)
//! \param[in] rawInputEvent The raw input event data
virtual void OnRawInputEvent(const AInputEvent* /*rawInputEvent*/) {}
////////////////////////////////////////////////////////////////////////////////////////////
//! Process raw text events (assumed to be dispatched on any thread)
//! \param[in] charsModifiedUTF8 The raw chars (encoded using modified UTF-8)
virtual void OnRawInputTextEvent(const char* /*charsModifiedUTF8*/) {}
////////////////////////////////////////////////////////////////////////////////////////////
//! Process raw virtual keyboard events (assumed to be dispatched on any thread)
//! \param[in] keyCode The key code of the virtual keyboard event
//! \param[in] keyAction The key action of the virtual keyboard event
virtual void OnRawInputVirtualKeyboardEvent(int /*keyCode*/, int /*keyAction*/) {}
};
using RawInputNotificationBusAndroid = AZ::EBus<RawInputNotificationsAndroid>;
} // 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/Input/Buses/Notifications/RawInputNotificationBus_Android.h>
@@ -0,0 +1,107 @@
/*
* 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/Devices/Gamepad/InputDeviceGamepad.h>
#include <AzFramework/Input/Buses/Notifications/RawInputNotificationBus_Platform.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace AzFramework
{
////////////////////////////////////////////////////////////////////////////////////////////////
//! Platform specific implementation for android game-pad input devices
class InputDeviceGamepadAndroid : public InputDeviceGamepad::Implementation
, public RawInputNotificationBusAndroid::Handler
{
public:
////////////////////////////////////////////////////////////////////////////////////////////
// Allocator
AZ_CLASS_ALLOCATOR(InputDeviceGamepadAndroid, AZ::SystemAllocator, 0);
////////////////////////////////////////////////////////////////////////////////////////////
//! Constructor
//! \param[in] inputDevice Reference to the input device being implemented
InputDeviceGamepadAndroid(InputDeviceGamepad& inputDevice);
////////////////////////////////////////////////////////////////////////////////////////////
//! Destructor
~InputDeviceGamepadAndroid() 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::TickInputDevice
void TickInputDevice() override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::RawInputNotificationsAndroid::OnRawInputEvent
void OnRawInputEvent(const AInputEvent* rawInputEvent) override;
};
////////////////////////////////////////////////////////////////////////////////////////////////
AZ::u32 InputDeviceGamepad::GetMaxSupportedGamepads()
{
// ToDo: Return the maximum number of supported gamepads
return 0;
}
////////////////////////////////////////////////////////////////////////////////////////////////
InputDeviceGamepad::Implementation* InputDeviceGamepad::Implementation::Create(
InputDeviceGamepad& inputDevice)
{
return aznew InputDeviceGamepadAndroid(inputDevice);
}
////////////////////////////////////////////////////////////////////////////////////////////////
InputDeviceGamepadAndroid::InputDeviceGamepadAndroid(InputDeviceGamepad& inputDevice)
: InputDeviceGamepad::Implementation(inputDevice)
{
}
////////////////////////////////////////////////////////////////////////////////////////////////
InputDeviceGamepadAndroid::~InputDeviceGamepadAndroid()
{
}
////////////////////////////////////////////////////////////////////////////////////////////////
bool InputDeviceGamepadAndroid::IsConnected() const
{
// ToDo: Figure out how/whether we can determine the availability of this game-pad,
// and implement this function (along with dispatching connect/disconnect events)
return false;
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceGamepadAndroid::SetVibration(float leftMotorSpeedNormalized,
float rightMotorSpeedNormalized)
{
// ToDo: Implement force-feedback if supported?
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceGamepadAndroid::TickInputDevice()
{
// ToDo: Process raw game-pad input and update input channels
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceGamepadAndroid::OnRawInputEvent(const AInputEvent* rawInputEvent)
{
// ToDo: Process raw game-pad input events
}
} // namespace AzFramework
@@ -0,0 +1,447 @@
/*
* 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/Devices/Keyboard/InputDeviceKeyboard.h>
#include <AzFramework/Input/Buses/Notifications/RawInputNotificationBus_Platform.h>
#include <AzCore/Android/Utils.h>
#include <android/input.h>
namespace
{
using namespace AzFramework;
// Table of key ids indexed by their android key code
const AZStd::array<const InputChannelId*, 285> InputChannelIdByKeyCodeTable =
{{
nullptr, // 0 AKEYCODE_UNKNOWN
nullptr, // 1 AKEYCODE_SOFT_LEFT
nullptr, // 2 AKEYCODE_SOFT_RIGHT
nullptr, // 3 AKEYCODE_HOME
nullptr, // 4 AKEYCODE_BACK
nullptr, // 5 AKEYCODE_CALL
nullptr, // 6 AKEYCODE_ENDCALL
&InputDeviceKeyboard::Key::Alphanumeric0, // 7 AKEYCODE_0
&InputDeviceKeyboard::Key::Alphanumeric1, // 8 AKEYCODE_1
&InputDeviceKeyboard::Key::Alphanumeric2, // 9 AKEYCODE_2
&InputDeviceKeyboard::Key::Alphanumeric3, // 10 AKEYCODE_3
&InputDeviceKeyboard::Key::Alphanumeric4, // 11 AKEYCODE_4
&InputDeviceKeyboard::Key::Alphanumeric5, // 12 AKEYCODE_5
&InputDeviceKeyboard::Key::Alphanumeric6, // 13 AKEYCODE_6
&InputDeviceKeyboard::Key::Alphanumeric7, // 14 AKEYCODE_7
&InputDeviceKeyboard::Key::Alphanumeric8, // 15 AKEYCODE_8
&InputDeviceKeyboard::Key::Alphanumeric9, // 16 AKEYCODE_9
nullptr, // 17 AKEYCODE_STAR
nullptr, // 18 AKEYCODE_POUND
&InputDeviceKeyboard::Key::NavigationArrowUp, // 19 AKEYCODE_DPAD_UP
&InputDeviceKeyboard::Key::NavigationArrowDown, // 20 AKEYCODE_DPAD_DOWN
&InputDeviceKeyboard::Key::NavigationArrowLeft, // 21 AKEYCODE_DPAD_LEFT
&InputDeviceKeyboard::Key::NavigationArrowRight, // 22 AKEYCODE_DPAD_RIGHT
nullptr, // 23 AKEYCODE_DPAD_CENTER
nullptr, // 24 AKEYCODE_VOLUME_UP
nullptr, // 25 AKEYCODE_VOLUME_DOWN
nullptr, // 26 AKEYCODE_POWER
nullptr, // 27 AKEYCODE_CAMERA
nullptr, // 28 AKEYCODE_CLEAR
&InputDeviceKeyboard::Key::AlphanumericA, // 29 AKEYCODE_A
&InputDeviceKeyboard::Key::AlphanumericB, // 30 AKEYCODE_B
&InputDeviceKeyboard::Key::AlphanumericC, // 31 AKEYCODE_C
&InputDeviceKeyboard::Key::AlphanumericD, // 32 AKEYCODE_D
&InputDeviceKeyboard::Key::AlphanumericE, // 33 AKEYCODE_E
&InputDeviceKeyboard::Key::AlphanumericF, // 34 AKEYCODE_F
&InputDeviceKeyboard::Key::AlphanumericG, // 35 AKEYCODE_G
&InputDeviceKeyboard::Key::AlphanumericH, // 36 AKEYCODE_H
&InputDeviceKeyboard::Key::AlphanumericI, // 37 AKEYCODE_I
&InputDeviceKeyboard::Key::AlphanumericJ, // 38 AKEYCODE_J
&InputDeviceKeyboard::Key::AlphanumericK, // 39 AKEYCODE_K
&InputDeviceKeyboard::Key::AlphanumericL, // 40 AKEYCODE_L
&InputDeviceKeyboard::Key::AlphanumericM, // 41 AKEYCODE_M
&InputDeviceKeyboard::Key::AlphanumericN, // 42 AKEYCODE_N
&InputDeviceKeyboard::Key::AlphanumericO, // 43 AKEYCODE_O
&InputDeviceKeyboard::Key::AlphanumericP, // 44 AKEYCODE_P
&InputDeviceKeyboard::Key::AlphanumericQ, // 45 AKEYCODE_Q
&InputDeviceKeyboard::Key::AlphanumericR, // 46 AKEYCODE_R
&InputDeviceKeyboard::Key::AlphanumericS, // 47 AKEYCODE_S
&InputDeviceKeyboard::Key::AlphanumericT, // 48 AKEYCODE_T
&InputDeviceKeyboard::Key::AlphanumericU, // 49 AKEYCODE_U
&InputDeviceKeyboard::Key::AlphanumericV, // 50 AKEYCODE_V
&InputDeviceKeyboard::Key::AlphanumericW, // 51 AKEYCODE_W
&InputDeviceKeyboard::Key::AlphanumericX, // 52 AKEYCODE_X
&InputDeviceKeyboard::Key::AlphanumericY, // 53 AKEYCODE_Y
&InputDeviceKeyboard::Key::AlphanumericZ, // 54 AKEYCODE_Z
&InputDeviceKeyboard::Key::PunctuationComma, // 55 AKEYCODE_COMMA
&InputDeviceKeyboard::Key::PunctuationPeriod, // 56 AKEYCODE_PERIOD
&InputDeviceKeyboard::Key::ModifierAltL, // 57 AKEYCODE_ALT_LEFT
&InputDeviceKeyboard::Key::ModifierAltR, // 58 AKEYCODE_ALT_RIGHT
&InputDeviceKeyboard::Key::ModifierShiftL, // 59 AKEYCODE_SHIFT_LEFT
&InputDeviceKeyboard::Key::ModifierShiftR, // 60 AKEYCODE_SHIFT_RIGHT
&InputDeviceKeyboard::Key::EditTab, // 61 AKEYCODE_TAB
&InputDeviceKeyboard::Key::EditSpace, // 62 AKEYCODE_SPACE
nullptr, // 63 AKEYCODE_SYM
nullptr, // 64 AKEYCODE_EXPLORER
nullptr, // 65 AKEYCODE_ENVELOPE
&InputDeviceKeyboard::Key::EditEnter, // 66 AKEYCODE_ENTER
&InputDeviceKeyboard::Key::EditBackspace, // 67 AKEYCODE_DEL
&InputDeviceKeyboard::Key::PunctuationTilde, // 68 AKEYCODE_GRAVE
&InputDeviceKeyboard::Key::PunctuationHyphen, // 69 AKEYCODE_MINUS
&InputDeviceKeyboard::Key::PunctuationEquals, // 70 AKEYCODE_EQUALS
&InputDeviceKeyboard::Key::PunctuationBracketL, // 71 AKEYCODE_LEFT_BRACKET
&InputDeviceKeyboard::Key::PunctuationBracketR, // 72 AKEYCODE_RIGHT_BRACKET
&InputDeviceKeyboard::Key::PunctuationBackslash, // 73 AKEYCODE_BACKSLASH
&InputDeviceKeyboard::Key::PunctuationSemicolon, // 74 AKEYCODE_SEMICOLON
&InputDeviceKeyboard::Key::PunctuationApostrophe, // 75 AKEYCODE_APOSTROPHE
&InputDeviceKeyboard::Key::PunctuationSlash, // 76 AKEYCODE_SLASH
nullptr, // 77 AKEYCODE_AT
nullptr, // 78 AKEYCODE_NUM
nullptr, // 79 AKEYCODE_HEADSETHOOK
nullptr, // 80 AKEYCODE_FOCUS
nullptr, // 81 AKEYCODE_PLUS
nullptr, // 82 AKEYCODE_MENU
nullptr, // 83 AKEYCODE_NOTIFICATION
nullptr, // 84 AKEYCODE_SEARCH
nullptr, // 85 AKEYCODE_MEDIA_PLAY_PAUSE
nullptr, // 86 AKEYCODE_MEDIA_STOP
nullptr, // 87 AKEYCODE_MEDIA_NEXT
nullptr, // 88 AKEYCODE_MEDIA_PREVIOUS
nullptr, // 89 AKEYCODE_MEDIA_REWIND
nullptr, // 90 AKEYCODE_MEDIA_FAST_FORWARD
nullptr, // 91 AKEYCODE_MUTE
&InputDeviceKeyboard::Key::NavigationPageUp, // 92 AKEYCODE_PAGE_UP
&InputDeviceKeyboard::Key::NavigationPageDown, // 93 AKEYCODE_PAGE_DOWN
nullptr, // 94 AKEYCODE_PICTSYMBOLS
nullptr, // 95 AKEYCODE_SWITCH_CHARSET
nullptr, // 96 AKEYCODE_BUTTON_A
nullptr, // 97 AKEYCODE_BUTTON_B
nullptr, // 98 AKEYCODE_BUTTON_C
nullptr, // 99 AKEYCODE_BUTTON_X
nullptr, // 100 AKEYCODE_BUTTON_Y
nullptr, // 101 AKEYCODE_BUTTON_Z
nullptr, // 102 AKEYCODE_BUTTON_L1
nullptr, // 103 AKEYCODE_BUTTON_R1
nullptr, // 104 AKEYCODE_BUTTON_L2
nullptr, // 105 AKEYCODE_BUTTON_R2
nullptr, // 106 AKEYCODE_BUTTON_THUMBL
nullptr, // 107 AKEYCODE_BUTTON_THUMBR
nullptr, // 108 AKEYCODE_BUTTON_START
nullptr, // 109 AKEYCODE_BUTTON_SELECT
nullptr, // 110 AKEYCODE_BUTTON_MODE
&InputDeviceKeyboard::Key::Escape, // 111 AKEYCODE_ESCAPE
&InputDeviceKeyboard::Key::NavigationDelete, // 112 AKEYCODE_FORWARD_DEL
&InputDeviceKeyboard::Key::ModifierCtrlL, // 113 AKEYCODE_CTRL_LEFT
&InputDeviceKeyboard::Key::ModifierCtrlR, // 114 AKEYCODE_CTRL_RIGHT
&InputDeviceKeyboard::Key::EditCapsLock, // 115 AKEYCODE_CAPS_LOCK
&InputDeviceKeyboard::Key::WindowsSystemScrollLock, // 116 AKEYCODE_SCROLL_LOCK
nullptr, // 117 AKEYCODE_META_LEFT
nullptr, // 118 AKEYCODE_META_RIGHT
nullptr, // 119 AKEYCODE_FUNCTION
nullptr, // 120 AKEYCODE_SYSRQ
nullptr, // 121 AKEYCODE_BREAK
&InputDeviceKeyboard::Key::NavigationHome, // 122 AKEYCODE_MOVE_HOME
&InputDeviceKeyboard::Key::NavigationEnd, // 123 AKEYCODE_MOVE_END
&InputDeviceKeyboard::Key::NavigationInsert, // 124 AKEYCODE_INSERT
nullptr, // 125 AKEYCODE_FORWARD
nullptr, // 126 AKEYCODE_MEDIA_PLAY
nullptr, // 127 AKEYCODE_MEDIA_PAUSE
nullptr, // 128 AKEYCODE_MEDIA_CLOSE
nullptr, // 129 AKEYCODE_MEDIA_EJECT
nullptr, // 130 AKEYCODE_MEDIA_RECORD
&InputDeviceKeyboard::Key::Function01, // 131 AKEYCODE_F1
&InputDeviceKeyboard::Key::Function02, // 132 AKEYCODE_F2
&InputDeviceKeyboard::Key::Function03, // 133 AKEYCODE_F3
&InputDeviceKeyboard::Key::Function04, // 134 AKEYCODE_F4
&InputDeviceKeyboard::Key::Function05, // 135 AKEYCODE_F5
&InputDeviceKeyboard::Key::Function06, // 136 AKEYCODE_F6
&InputDeviceKeyboard::Key::Function07, // 137 AKEYCODE_F7
&InputDeviceKeyboard::Key::Function08, // 138 AKEYCODE_F8
&InputDeviceKeyboard::Key::Function09, // 139 AKEYCODE_F9
&InputDeviceKeyboard::Key::Function10, // 140 AKEYCODE_F10
&InputDeviceKeyboard::Key::Function11, // 141 AKEYCODE_F11
&InputDeviceKeyboard::Key::Function12, // 142 AKEYCODE_F12
&InputDeviceKeyboard::Key::NumLock, // 143 AKEYCODE_NUM_LOCK
&InputDeviceKeyboard::Key::NumPad0, // 144 AKEYCODE_NUMPAD_0
&InputDeviceKeyboard::Key::NumPad1, // 145 AKEYCODE_NUMPAD_1
&InputDeviceKeyboard::Key::NumPad2, // 146 AKEYCODE_NUMPAD_2
&InputDeviceKeyboard::Key::NumPad3, // 147 AKEYCODE_NUMPAD_3
&InputDeviceKeyboard::Key::NumPad4, // 148 AKEYCODE_NUMPAD_4
&InputDeviceKeyboard::Key::NumPad5, // 149 AKEYCODE_NUMPAD_5
&InputDeviceKeyboard::Key::NumPad6, // 150 AKEYCODE_NUMPAD_6
&InputDeviceKeyboard::Key::NumPad7, // 151 AKEYCODE_NUMPAD_7
&InputDeviceKeyboard::Key::NumPad8, // 152 AKEYCODE_NUMPAD_8
&InputDeviceKeyboard::Key::NumPad9, // 153 AKEYCODE_NUMPAD_9
&InputDeviceKeyboard::Key::NumPadDivide, // 154 AKEYCODE_NUMPAD_DIVIDE
&InputDeviceKeyboard::Key::NumPadMultiply, // 155 AKEYCODE_NUMPAD_MULTIPLY
&InputDeviceKeyboard::Key::NumPadSubtract, // 156 AKEYCODE_NUMPAD_SUBTRACT
&InputDeviceKeyboard::Key::NumPadAdd, // 157 AKEYCODE_NUMPAD_ADD
&InputDeviceKeyboard::Key::NumPadDecimal, // 158 AKEYCODE_NUMPAD_DOT
nullptr, // 159 AKEYCODE_NUMPAD_COMMA
&InputDeviceKeyboard::Key::NumPadEnter, // 160 AKEYCODE_NUMPAD_ENTER
nullptr, // 161 AKEYCODE_NUMPAD_EQUALS
nullptr, // 162 AKEYCODE_NUMPAD_LEFT_PAREN
nullptr, // 163 AKEYCODE_NUMPAD_RIGHT_PAREN
nullptr, // 164 AKEYCODE_VOLUME_MUTE
nullptr, // 165 AKEYCODE_INFO
nullptr, // 166 AKEYCODE_CHANNEL_UP
nullptr, // 167 AKEYCODE_CHANNEL_DOWN
nullptr, // 168 AKEYCODE_ZOOM_IN
nullptr, // 169 AKEYCODE_ZOOM_OUT
nullptr, // 170 AKEYCODE_TV
nullptr, // 171 AKEYCODE_WINDOW
nullptr, // 172 AKEYCODE_GUIDE
nullptr, // 173 AKEYCODE_DVR
nullptr, // 174 AKEYCODE_BOOKMARK
nullptr, // 175 AKEYCODE_CAPTIONS
nullptr, // 176 AKEYCODE_SETTINGS
nullptr, // 177 AKEYCODE_TV_POWER
nullptr, // 178 AKEYCODE_TV_INPUT
nullptr, // 179 AKEYCODE_STB_POWER
nullptr, // 180 AKEYCODE_STB_INPUT
nullptr, // 181 AKEYCODE_AVR_POWER
nullptr, // 182 AKEYCODE_AVR_INPUT
nullptr, // 183 AKEYCODE_PROG_RED
nullptr, // 184 AKEYCODE_PROG_GREEN
nullptr, // 185 AKEYCODE_PROG_YELLOW
nullptr, // 186 AKEYCODE_PROG_BLUE
nullptr, // 187 AKEYCODE_APP_SWITCH
nullptr, // 188 AKEYCODE_BUTTON_1
nullptr, // 189 AKEYCODE_BUTTON_2
nullptr, // 190 AKEYCODE_BUTTON_3
nullptr, // 191 AKEYCODE_BUTTON_4
nullptr, // 192 AKEYCODE_BUTTON_5
nullptr, // 193 AKEYCODE_BUTTON_6
nullptr, // 194 AKEYCODE_BUTTON_7
nullptr, // 195 AKEYCODE_BUTTON_8
nullptr, // 196 AKEYCODE_BUTTON_9
nullptr, // 197 AKEYCODE_BUTTON_10
nullptr, // 198 AKEYCODE_BUTTON_11
nullptr, // 199 AKEYCODE_BUTTON_12
nullptr, // 200 AKEYCODE_BUTTON_13
nullptr, // 201 AKEYCODE_BUTTON_14
nullptr, // 202 AKEYCODE_BUTTON_15
nullptr, // 203 AKEYCODE_BUTTON_16
nullptr, // 204 AKEYCODE_LANGUAGE_SWITCH
nullptr, // 205 AKEYCODE_MANNER_MODE
nullptr, // 206 AKEYCODE_3D_MODE
nullptr, // 207 AKEYCODE_CONTACTS
nullptr, // 208 AKEYCODE_CALENDAR
nullptr, // 209 AKEYCODE_MUSIC
nullptr, // 210 AKEYCODE_CALCULATOR
nullptr, // 211 AKEYCODE_ZENKAKU_HANKAKU
nullptr, // 212 AKEYCODE_EISU
nullptr, // 213 AKEYCODE_MUHENKAN
nullptr, // 214 AKEYCODE_HENKAN
nullptr, // 215 AKEYCODE_KATAKANA_HIRAGANA
nullptr, // 216 AKEYCODE_YEN
nullptr, // 217 AKEYCODE_RO
nullptr, // 218 AKEYCODE_KANA
nullptr, // 219 AKEYCODE_ASSIST
nullptr, // 220 AKEYCODE_BRIGHTNESS_DOWN
nullptr, // 221 AKEYCODE_BRIGHTNESS_UP
nullptr, // 222 AKEYCODE_MEDIA_AUDIO_TRACK
nullptr, // 223 AKEYCODE_SLEEP
nullptr, // 224 AKEYCODE_WAKEUP
nullptr, // 225 AKEYCODE_PAIRING
nullptr, // 226 AKEYCODE_MEDIA_TOP_MENU
nullptr, // 227 AKEYCODE_11
nullptr, // 228 AKEYCODE_12
nullptr, // 229 AKEYCODE_LAST_CHANNEL
nullptr, // 230 AKEYCODE_TV_DATA_SERVICE
nullptr, // 231 AKEYCODE_VOICE_ASSIST
nullptr, // 232 AKEYCODE_TV_RADIO_SERVICE
nullptr, // 233 AKEYCODE_TV_TELETEXT
nullptr, // 234 AKEYCODE_TV_NUMBER_ENTRY
nullptr, // 235 AKEYCODE_TV_TERRESTRIAL_ANALOG
nullptr, // 236 AKEYCODE_TV_TERRESTRIAL_DIGITAL
nullptr, // 237 AKEYCODE_TV_SATELLITE
nullptr, // 238 AKEYCODE_TV_SATELLITE_BS
nullptr, // 239 AKEYCODE_TV_SATELLITE_CS
nullptr, // 240 AKEYCODE_TV_SATELLITE_SERVICE
nullptr, // 241 AKEYCODE_TV_NETWORK
nullptr, // 242 AKEYCODE_TV_ANTENNA_CABLE
nullptr, // 243 AKEYCODE_TV_INPUT_HDMI_1
nullptr, // 244 AKEYCODE_TV_INPUT_HDMI_2
nullptr, // 245 AKEYCODE_TV_INPUT_HDMI_3
nullptr, // 246 AKEYCODE_TV_INPUT_HDMI_4
nullptr, // 247 AKEYCODE_TV_INPUT_COMPOSITE_1
nullptr, // 248 AKEYCODE_TV_INPUT_COMPOSITE_2
nullptr, // 249 AKEYCODE_TV_INPUT_COMPONENT_1
nullptr, // 250 AKEYCODE_TV_INPUT_COMPONENT_2
nullptr, // 251 AKEYCODE_TV_INPUT_VGA_1
nullptr, // 252 AKEYCODE_TV_AUDIO_DESCRIPTION
nullptr, // 253 AKEYCODE_TV_AUDIO_DESCRIPTION_MIX_UP
nullptr, // 254 AKEYCODE_TV_AUDIO_DESCRIPTION_MIX_DOWN
nullptr, // 255 AKEYCODE_TV_ZOOM_MODE
nullptr, // 256 AKEYCODE_TV_CONTENTS_MENU
nullptr, // 257 AKEYCODE_TV_MEDIA_CONTEXT_MENU
nullptr, // 258 AKEYCODE_TV_TIMER_PROGRAMMING
nullptr, // 259 AKEYCODE_HELP
nullptr, // 260 AKEYCODE_NAVIGATE_PREVIOUS
nullptr, // 261 AKEYCODE_NAVIGATE_NEXT
nullptr, // 262 AKEYCODE_NAVIGATE_IN
nullptr, // 263 AKEYCODE_NAVIGATE_OUT
nullptr, // 264 AKEYCODE_STEM_PRIMARY
nullptr, // 265 AKEYCODE_STEM_1
nullptr, // 266 AKEYCODE_STEM_2
nullptr, // 267 AKEYCODE_STEM_3
nullptr, // 268 AKEYCODE_DPAD_UP_LEFT
nullptr, // 269 AKEYCODE_DPAD_DOWN_LEFT
nullptr, // 270 AKEYCODE_DPAD_UP_RIGHT
nullptr, // 271 AKEYCODE_DPAD_DOWN_RIGHT
nullptr, // 272 AKEYCODE_MEDIA_SKIP_FORWARD
nullptr, // 273 AKEYCODE_MEDIA_SKIP_BACKWARD
nullptr, // 274 AKEYCODE_MEDIA_STEP_FORWARD
nullptr, // 275 AKEYCODE_MEDIA_STEP_BACKWARD
nullptr, // 276 AKEYCODE_SOFT_SLEEP
nullptr, // 277 AKEYCODE_CUT
nullptr, // 278 AKEYCODE_COPY
nullptr, // 279 AKEYCODE_PASTE
nullptr, // 280 AKEYCODE_SYSTEM_NAVIGATION_UP
nullptr, // 281 AKEYCODE_SYSTEM_NAVIGATION_DOWN
nullptr, // 282 AKEYCODE_SYSTEM_NAVIGATION_LEFT
nullptr, // 283 AKEYCODE_SYSTEM_NAVIGATION_RIGHT
nullptr, // 284 AKEYCODE_ALL_APPS
}};
}
namespace AzFramework
{
//! Platform specific implementation for Android physical keyboard input devices. This
//! includes devices connected through USB or Bluetooth. This input device is responsible
//! for sending key events only, the virtual keyboard is the one responsible for sending
//! text events. Should this behaviour need to be changed, LY-69260 will correct it
class InputDeviceKeyboardAndroid
: public InputDeviceKeyboard::Implementation
, public RawInputNotificationBusAndroid::Handler
{
public:
AZ_CLASS_ALLOCATOR(InputDeviceKeyboardAndroid, AZ::SystemAllocator, 0);
InputDeviceKeyboardAndroid(InputDeviceKeyboard& inputDevice);
~InputDeviceKeyboardAndroid() override;
//! \ref AzFramework::RawInputNotificationsAndroid::OnRawInputEvent
void OnRawInputEvent(const AInputEvent* rawInputEvent) 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;
bool m_hasTextEntryStarted = false; //!< Has text entry been started?
};
InputDeviceKeyboardAndroid::InputDeviceKeyboardAndroid(InputDeviceKeyboard& inputDevice)
: InputDeviceKeyboard::Implementation(inputDevice)
, m_hasTextEntryStarted(false)
{
RawInputNotificationBusAndroid::Handler::BusConnect();
}
InputDeviceKeyboardAndroid::~InputDeviceKeyboardAndroid()
{
RawInputNotificationBusAndroid::Handler::BusDisconnect();
}
void InputDeviceKeyboardAndroid::OnRawInputEvent(const AInputEvent* rawInputEvent)
{
// don't bother with physical keyboard events if it's not connected
if (!IsConnected())
{
return;
}
// only care about key events
int eventType = AInputEvent_getType(rawInputEvent);
if (eventType != AINPUT_EVENT_TYPE_KEY)
{
return;
}
int keyCode = AKeyEvent_getKeyCode(rawInputEvent);
const InputChannelId* channelId = (keyCode < InputChannelIdByKeyCodeTable.size()) ?
InputChannelIdByKeyCodeTable[keyCode] : nullptr;
if (channelId)
{
int action = AKeyEvent_getAction(rawInputEvent);
switch (action)
{
case AKEY_EVENT_ACTION_DOWN:
QueueRawKeyEvent(*channelId, true);
break;
case AKEY_EVENT_ACTION_UP:
QueueRawKeyEvent(*channelId, false);
break;
// The multiple event is sent for 2 reasons, both of which we don't care about
// 1. When the keycode is unknown - a complex string event e.g non-standard english characters
// 2. When the keycode is known - there were multiple duplicate key events (unlikely)
case AKEY_EVENT_ACTION_MULTIPLE:
default:
break;
}
}
}
bool InputDeviceKeyboardAndroid::IsConnected() const
{
AConfiguration* config = AZ::Android::Utils::GetConfiguration();
int keyboard = AConfiguration_getKeyboard(config);
return (keyboard != ACONFIGURATION_KEYBOARD_NOKEYS); // nokeys == no physical keyboard connected
}
bool InputDeviceKeyboardAndroid::HasTextEntryStarted() const
{
return m_hasTextEntryStarted;
}
void InputDeviceKeyboardAndroid::TextEntryStart(const InputTextEntryRequests::VirtualKeyboardOptions& options)
{
m_hasTextEntryStarted = true;
}
void InputDeviceKeyboardAndroid::TextEntryStop()
{
m_hasTextEntryStarted = false;
}
void InputDeviceKeyboardAndroid::TickInputDevice()
{
ProcessRawEventQueues();
}
InputDeviceKeyboard::Implementation* InputDeviceKeyboard::Implementation::Create(InputDeviceKeyboard& inputDevice)
{
return aznew InputDeviceKeyboardAndroid(inputDevice);
}
} // namespace AzFramework
@@ -0,0 +1,447 @@
/*
* 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/Devices/Motion/InputDeviceMotion.h>
#include <AzCore/Android/JNI/JNI.h>
#include <AzCore/Android/JNI/Object.h>
#include <AzCore/Android/Utils.h>
#include <AzCore/Debug/Trace.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace AzFramework
{
////////////////////////////////////////////////////////////////////////////////////////////////
//! Platform specific implementation for android motion input devices
class InputDeviceMotionAndroid : public InputDeviceMotion::Implementation
{
public:
////////////////////////////////////////////////////////////////////////////////////////////
// Allocator
AZ_CLASS_ALLOCATOR(InputDeviceMotionAndroid, AZ::SystemAllocator, 0);
////////////////////////////////////////////////////////////////////////////////////////////
//! Constructor
//! \param[in] inputDevice Reference to the input device being implemented
InputDeviceMotionAndroid(InputDeviceMotion& inputDevice);
////////////////////////////////////////////////////////////////////////////////////////////
//! Destructor
~InputDeviceMotionAndroid() override;
private:
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceMotion::Implementation::IsConnected
bool IsConnected() const override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceMotion::Implementation::TickInputDevice
void TickInputDevice() override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceMotion::Implementation::RefreshMotionSensors
void RefreshMotionSensors(const InputDeviceRequests::InputChannelIdSet& enabledChannelIds) override;
////////////////////////////////////////////////////////////////////////////////////////////
//! Get the sensor state that is expected by the Java motion manager's RefreshMotionSensors
//! \param[in] wasRequired Was the sensor required when RefreshMotionSensors was last called?
//! \param[in] isRequired Is the sensor now required?
//! \return 1 if the sensor should be enabled, -1 if it should be disabled, 0 for no change
int GetUpdatedSensorState(bool wasRequired, bool isRequired) const;
////////////////////////////////////////////////////////////////////////////////////////////
///@{
//! Check whether a particular sensor is required based on the values of m_enabledChannelIds
//! \return True if the sensor should be enabled, false otherwise
bool IsRequiredAccelerationRaw() const;
bool IsRequiredAccelerationUser() const;
bool IsRequiredAccelerationGravity() const;
bool IsRequiredRotationRateRaw() const;
bool IsRequiredRotationRateUnbiased() const;
bool IsRequiredMagneticFieldRaw() const;
bool IsRequiredMagneticFieldUnbiased() const;
bool IsRequiredOrientationCurrent() const;
///@}
////////////////////////////////////////////////////////////////////////////////////////////
//! Indicies into the motion sensor data returned from the Java code as a packed float array.
//!
//! While we would (ideally) like to encapsulate and return all this data using a Java class,
//! we would then need to 'reach back' through the JNI for each field to access the raw data.
//! Packing all the data into a float array is more efficient, but at the expense of needing
//! to access data using pre-defined array indices, instead of explicitly named class fields.
//!
//! But while explicitly named class fields provide a modicum of safety, lots of boilerplate
//! code is needed, and while support for additional sensor data may be added in the future,
//! the existing set is unlikely to change, so combined with the above mentioned performance
//! consideration this approach to obtaining raw sensor data seems preferable on most fronts.
enum PackedSensorDataIndex
{
Index_AccelerationRawUpdated = 0,
Index_AccelerationRawX = 1,
Index_AccelerationRawY = 2,
Index_AccelerationRawZ = 3,
Index_AccelerationUserUpdated = 4,
Index_AccelerationUserX = 5,
Index_AccelerationUserY = 6,
Index_AccelerationUserZ = 7,
Index_AccelerationGravityUpdated = 8,
Index_AccelerationGravityX = 9,
Index_AccelerationGravityY = 10,
Index_AccelerationGravityZ = 11,
Index_RotationRateRawUpdated = 12,
Index_RotationRateRawX = 13,
Index_RotationRateRawY = 14,
Index_RotationRateRawZ = 15,
Index_RotationRateUnbiasedUpdated = 16,
Index_RotationRateUnbiasedX = 17,
Index_RotationRateUnbiasedY = 18,
Index_RotationRateUnbiasedZ = 19,
Index_MagneticFieldRawUpdated = 20,
Index_MagneticFieldRawX = 21,
Index_MagneticFieldRawY = 22,
Index_MagneticFieldRawZ = 23,
Index_MagneticFieldUnbiasedUpdated = 24,
Index_MagneticFieldUnbiasedX = 25,
Index_MagneticFieldUnbiasedY = 26,
Index_MagneticFieldUnbiasedZ = 27,
Index_OrientationUpdated = 28,
Index_OrientationX = 29,
Index_OrientationY = 30,
Index_OrientationZ = 31,
Index_OrientationW = 32,
Index_OrientationAdjustmentRadiansZ = 33,
// Length of the packed sensor data array
PackedSensorDataLength
};
////////////////////////////////////////////////////////////////////////////////////////////
//! The set of currently enabled input channel ids
InputDeviceRequests::InputChannelIdSet m_enabledChannelIds;
////////////////////////////////////////////////////////////////////////////////////////////
//! The packed sensor data array. This is a member variable to avoid creating it every frame.
float* m_packedSensorDataArray;
////////////////////////////////////////////////////////////////////////////////////////////
//! JNI object reference to the motion sensor manager
AZStd::unique_ptr<AZ::Android::JNI::Object> m_motionSensorManager;
////////////////////////////////////////////////////////////////////////////////////////////
///@{
//! Java field / function name
static constexpr const char* s_javaFuntionNameGetMotionSensorManager = "GetMotionSensorManager";
static constexpr const char* s_javaFuntionNameRefreshMotionSensors = "RefreshMotionSensors";
static constexpr const char* s_javaFuntionNameRequestLatestMotionSensorData = "RequestLatestMotionSensorData";
static constexpr const char* s_javaFieldNameMotionSensorDataPackedLength = "MOTION_SENSOR_DATA_PACKED_LENGTH";
///@}
};
////////////////////////////////////////////////////////////////////////////////////////////////
InputDeviceMotion::Implementation* InputDeviceMotion::Implementation::Create(
InputDeviceMotion& inputDevice)
{
return aznew InputDeviceMotionAndroid(inputDevice);
}
////////////////////////////////////////////////////////////////////////////////////////////////
InputDeviceMotionAndroid::InputDeviceMotionAndroid(InputDeviceMotion& inputDevice)
: InputDeviceMotion::Implementation(inputDevice)
, m_enabledChannelIds()
, m_packedSensorDataArray(nullptr)
, m_motionSensorManager()
{
// Create a JNI object reference to the motion sensor manager
m_motionSensorManager.reset(aznew AZ::Android::JNI::Object("com/amazon/lumberyard/input/MotionSensorManager"));
// Regsiter the Java methods that are required to enable and query for motion sensor data
m_motionSensorManager->RegisterMethod(s_javaFuntionNameRefreshMotionSensors, "(FIIIIIIII)V");
m_motionSensorManager->RegisterMethod(s_javaFuntionNameRequestLatestMotionSensorData, "()[F");
m_motionSensorManager->RegisterStaticField(s_javaFieldNameMotionSensorDataPackedLength, "I");
// Create the packed sensor data array
const int packedSensorDataLength = m_motionSensorManager->GetStaticIntField(s_javaFieldNameMotionSensorDataPackedLength);
AZ_Assert(packedSensorDataLength == PackedSensorDataLength,
"InputDeviceMotionAndroid::PackedSensorDataLength != MotionSensorManager::MOTION_SENSOR_DATA_PACKED_LENGTH");
m_packedSensorDataArray = new float[packedSensorDataLength];
// create the java instance
bool ret = m_motionSensorManager->CreateInstance("(Landroid/app/Activity;)V", AZ::Android::Utils::GetActivityRef());
AZ_Assert(ret, "Failed to create the MotionSensorManager Java instance.");
}
////////////////////////////////////////////////////////////////////////////////////////////////
InputDeviceMotionAndroid::~InputDeviceMotionAndroid()
{
// Destroy the packed sensor data array
delete[] m_packedSensorDataArray;
// Destroy the JNI object reference to the motion sensor manager
m_motionSensorManager->DestroyInstance();
}
////////////////////////////////////////////////////////////////////////////////////////////////
bool InputDeviceMotionAndroid::IsConnected() const
{
// Motion input is always available on android
return true;
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceMotionAndroid::TickInputDevice()
{
if (m_enabledChannelIds.empty())
{
// Early out to avoid expensive JNI calls.
return;
}
if (JNIEnv* jniEnv = AZ::Android::JNI::GetEnv())
{
// Get the latest sensor data through JNI
jfloatArray latestSensorData = m_motionSensorManager->InvokeObjectMethod<jfloatArray>(s_javaFuntionNameRequestLatestMotionSensorData);
AZ_Assert(jniEnv->GetArrayLength(latestSensorData) == PackedSensorDataLength,
"InputDeviceMotionAndroid::PackedSensorDataLength != MotionSensorManager::MOTION_SENSOR_DATA_PACKED_LENGTH");
jniEnv->GetFloatArrayRegion(latestSensorData, 0, PackedSensorDataLength, m_packedSensorDataArray);
AZ_Assert(!jniEnv->ExceptionCheck(), "JNI exception thrown while getting the latest sensor data");
jniEnv->DeleteGlobalRef(latestSensorData);
}
else
{
// The JNI environment is not valid
return;
}
// While we would (ideally) like to encapsulate and return all this data using a Java class,
// we would then need to 'reach back' through the JNI for each field to access the raw data.
// Packing all the data into a float array is more efficient, but at the expense of needing
// to access data using pre-defined array indices, instead of explicitly named class fields.
//
// But while explicitly named class fields provide a modicum of safety, lots of boilerplate
// code is needed, and while support for additional sensor data may be added in the future,
// the existing set is unlikely to change, so combined with the above mentioned performance
// consideration this approach to obtaining raw sensor data seems preferable on most fronts.
if (m_packedSensorDataArray[Index_AccelerationRawUpdated])
{
// Process raw acceleration
const AZ::Vector3 sensorData(m_packedSensorDataArray[Index_AccelerationRawX],
m_packedSensorDataArray[Index_AccelerationRawY],
m_packedSensorDataArray[Index_AccelerationRawZ]);
ProcessAccelerationData(InputDeviceMotion::Acceleration::Raw, sensorData);
}
if (m_packedSensorDataArray[Index_AccelerationUserUpdated])
{
// Process user acceleration
const AZ::Vector3 sensorData(m_packedSensorDataArray[Index_AccelerationUserX],
m_packedSensorDataArray[Index_AccelerationUserY],
m_packedSensorDataArray[Index_AccelerationUserZ]);
ProcessAccelerationData(InputDeviceMotion::Acceleration::User, sensorData);
}
if (m_packedSensorDataArray[Index_AccelerationGravityUpdated])
{
// Process gravity acceleration
const AZ::Vector3 sensorData(m_packedSensorDataArray[Index_AccelerationGravityX],
m_packedSensorDataArray[Index_AccelerationGravityY],
m_packedSensorDataArray[Index_AccelerationGravityZ]);
ProcessAccelerationData(InputDeviceMotion::Acceleration::Gravity, sensorData);
}
if (m_packedSensorDataArray[Index_RotationRateRawUpdated])
{
// Process raw rotation rate
const AZ::Vector3 sensorData(m_packedSensorDataArray[Index_RotationRateRawX],
m_packedSensorDataArray[Index_RotationRateRawY],
m_packedSensorDataArray[Index_RotationRateRawZ]);
ProcessRotationRateData(InputDeviceMotion::RotationRate::Raw, sensorData);
}
if (m_packedSensorDataArray[Index_RotationRateUnbiasedUpdated])
{
// Process unbiased rotation rate
const AZ::Vector3 sensorData(m_packedSensorDataArray[Index_RotationRateUnbiasedX],
m_packedSensorDataArray[Index_RotationRateUnbiasedY],
m_packedSensorDataArray[Index_RotationRateUnbiasedZ]);
ProcessRotationRateData(InputDeviceMotion::RotationRate::Unbiased, sensorData);
}
if (m_packedSensorDataArray[Index_MagneticFieldRawUpdated])
{
// Process raw magnetic field
const AZ::Vector3 sensorData(m_packedSensorDataArray[Index_MagneticFieldRawX],
m_packedSensorDataArray[Index_MagneticFieldRawY],
m_packedSensorDataArray[Index_MagneticFieldRawZ]);
ProcessMagneticFieldData(InputDeviceMotion::MagneticField::Raw, sensorData);
}
if (m_packedSensorDataArray[Index_MagneticFieldUnbiasedUpdated])
{
// Process unbiased magnetic field
const AZ::Vector3 sensorData(m_packedSensorDataArray[Index_MagneticFieldUnbiasedX],
m_packedSensorDataArray[Index_MagneticFieldUnbiasedY],
m_packedSensorDataArray[Index_MagneticFieldUnbiasedZ]);
ProcessMagneticFieldData(InputDeviceMotion::MagneticField::Unbiased, sensorData);
}
if (m_packedSensorDataArray[Index_AccelerationGravityUpdated] &&
m_packedSensorDataArray[Index_MagneticFieldUnbiasedUpdated])
{
// Calculate and process magnetic north
const AZ::Vector3 gravity(m_packedSensorDataArray[Index_AccelerationGravityX],
m_packedSensorDataArray[Index_AccelerationGravityY],
m_packedSensorDataArray[Index_AccelerationGravityZ]);
const AZ::Vector3 magneticField(m_packedSensorDataArray[Index_MagneticFieldUnbiasedX],
m_packedSensorDataArray[Index_MagneticFieldUnbiasedY],
m_packedSensorDataArray[Index_MagneticFieldUnbiasedZ]);
const AZ::Vector3 gravityNormalized = gravity.GetNormalized();
const AZ::Vector3 magneticFieldNormalized = magneticField.GetNormalized();
const AZ::Vector3 magneticEastNormalized = gravityNormalized.Cross(magneticFieldNormalized).GetNormalized();
const AZ::Vector3 magneticNorthNormalized = magneticEastNormalized.Cross(gravityNormalized).GetNormalized();
ProcessMagneticFieldData(InputDeviceMotion::MagneticField::North, magneticNorthNormalized);
}
if (m_packedSensorDataArray[Index_OrientationUpdated])
{
// Process current orientation
const AZ::Vector3 sensorDataXYZ(m_packedSensorDataArray[Index_OrientationX],
m_packedSensorDataArray[Index_OrientationY],
m_packedSensorDataArray[Index_OrientationZ]);
const float sensorDataImaginary(m_packedSensorDataArray[Index_OrientationW]);
AZ::Quaternion sensorData = AZ::Quaternion::CreateFromVector3AndValue(sensorDataXYZ,
sensorDataImaginary);
// Android doesn't provide us with any quaternion math,
// so we adjust alignment here instead of the Java code.
const float rotationAdjustmentZ(m_packedSensorDataArray[Index_OrientationAdjustmentRadiansZ]);
sensorData *= AZ::Quaternion::CreateRotationZ(rotationAdjustmentZ);
ProcessOrientationData(InputDeviceMotion::Orientation::Current, sensorData);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceMotionAndroid::RefreshMotionSensors(
const InputDeviceRequests::InputChannelIdSet& enabledChannelIds)
{
if (m_enabledChannelIds == enabledChannelIds)
{
// Early out to avoid expensive JNI calls.
return;
}
const bool wasRequiredAccelerationRaw = IsRequiredAccelerationRaw();
const bool wasRequiredAccelerationUser = IsRequiredAccelerationUser();
const bool wasRequiredAccelerationGravity = IsRequiredAccelerationGravity();
const bool wasRequiredRotationRateRaw = IsRequiredRotationRateRaw();
const bool wasRequiredRotationRateUnbiased = IsRequiredRotationRateUnbiased();
const bool wasRequiredMagneticFieldRaw = IsRequiredMagneticFieldRaw();
const bool wasRequiredMagneticFieldUnbiased = IsRequiredMagneticFieldUnbiased();
const bool wasRequiredOrientationCurrent = IsRequiredOrientationCurrent();
m_enabledChannelIds = enabledChannelIds;
const bool isRequiredAccelerationRaw = IsRequiredAccelerationRaw();
const bool isRequiredAccelerationUser = IsRequiredAccelerationUser();
const bool isRequiredAccelerationGravity = IsRequiredAccelerationGravity();
const bool isRequiredRotationRateRaw = IsRequiredRotationRateRaw();
const bool isRequiredRotationRateUnbiased = IsRequiredRotationRateUnbiased();
const bool isRequiredMagneticFieldRaw = IsRequiredMagneticFieldRaw();
const bool isRequiredMagneticFieldUnbiased = IsRequiredMagneticFieldUnbiased();
const bool isRequiredOrientationCurrent = IsRequiredOrientationCurrent();
static const float s_desiredMotionSensorUpdateIntervalSeconds = 1.0f / 30.0f;
m_motionSensorManager->InvokeVoidMethod(
s_javaFuntionNameRefreshMotionSensors,
s_desiredMotionSensorUpdateIntervalSeconds,
GetUpdatedSensorState(wasRequiredAccelerationRaw, isRequiredAccelerationRaw),
GetUpdatedSensorState(wasRequiredAccelerationUser, isRequiredAccelerationUser),
GetUpdatedSensorState(wasRequiredAccelerationGravity, isRequiredAccelerationGravity),
GetUpdatedSensorState(wasRequiredRotationRateRaw, isRequiredRotationRateRaw),
GetUpdatedSensorState(wasRequiredRotationRateUnbiased, isRequiredRotationRateUnbiased),
GetUpdatedSensorState(wasRequiredMagneticFieldRaw, isRequiredMagneticFieldRaw),
GetUpdatedSensorState(wasRequiredMagneticFieldUnbiased, isRequiredMagneticFieldUnbiased),
GetUpdatedSensorState(wasRequiredOrientationCurrent, isRequiredOrientationCurrent));
}
////////////////////////////////////////////////////////////////////////////////////////////////
int InputDeviceMotionAndroid::GetUpdatedSensorState(bool wasRequired, bool isRequired) const
{
if (wasRequired == isRequired)
{
return 0; // Unchanged
}
else if (isRequired)
{
return 1; // Enable
}
else
{
return -1; // Disable
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
bool InputDeviceMotionAndroid::IsRequiredAccelerationRaw() const
{
return m_enabledChannelIds.find(InputDeviceMotion::Acceleration::Raw) != m_enabledChannelIds.end();
}
////////////////////////////////////////////////////////////////////////////////////////////////
bool InputDeviceMotionAndroid::IsRequiredAccelerationUser() const
{
return m_enabledChannelIds.find(InputDeviceMotion::Acceleration::User) != m_enabledChannelIds.end();
}
////////////////////////////////////////////////////////////////////////////////////////////////
bool InputDeviceMotionAndroid::IsRequiredAccelerationGravity() const
{
return m_enabledChannelIds.find(InputDeviceMotion::Acceleration::Gravity) != m_enabledChannelIds.end() ||
m_enabledChannelIds.find(InputDeviceMotion::MagneticField::North) != m_enabledChannelIds.end();
}
////////////////////////////////////////////////////////////////////////////////////////////////
bool InputDeviceMotionAndroid::IsRequiredRotationRateRaw() const
{
return m_enabledChannelIds.find(InputDeviceMotion::RotationRate::Raw) != m_enabledChannelIds.end();
}
////////////////////////////////////////////////////////////////////////////////////////////////
bool InputDeviceMotionAndroid::IsRequiredRotationRateUnbiased() const
{
return m_enabledChannelIds.find(InputDeviceMotion::RotationRate::Unbiased) != m_enabledChannelIds.end();
}
////////////////////////////////////////////////////////////////////////////////////////////////
bool InputDeviceMotionAndroid::IsRequiredMagneticFieldRaw() const
{
return m_enabledChannelIds.find(InputDeviceMotion::MagneticField::Raw) != m_enabledChannelIds.end();
}
////////////////////////////////////////////////////////////////////////////////////////////////
bool InputDeviceMotionAndroid::IsRequiredMagneticFieldUnbiased() const
{
return m_enabledChannelIds.find(InputDeviceMotion::MagneticField::Unbiased) != m_enabledChannelIds.end() ||
m_enabledChannelIds.find(InputDeviceMotion::MagneticField::North) != m_enabledChannelIds.end();
}
////////////////////////////////////////////////////////////////////////////////////////////////
bool InputDeviceMotionAndroid::IsRequiredOrientationCurrent() const
{
return m_enabledChannelIds.find(InputDeviceMotion::Orientation::Current) != m_enabledChannelIds.end();
}
} // namespace AzFramework
@@ -0,0 +1,370 @@
/*
* 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/Devices/Mouse/InputDeviceMouse.h>
#include <AzFramework/Input/Buses/Notifications/RawInputNotificationBus_Platform.h>
#include <AzCore/Android/JNI/Object.h>
#include <AzCore/Android/ApiLevel.h>
#include <AzCore/Android/Utils.h>
#include <android/input.h>
namespace
{
// the following values were added in later APIs however their native counterparts
// were only added to the unified headers in NDK r14+
// dedicated mouse button events were added in API 23
const int EVENT_ACTION_BUTTON_PRESS = 11; // AMOTION_EVENT_ACTION_BUTTON_PRESS
const int EVENT_ACTION_BUTTON_RELEASE = 12; // AMOTION_EVENT_ACTION_BUTTON_RELEASE
// relative pointer queries were added in API 24
const int EVENT_AXIS_RELATIVE_X = 27; // AMOTION_EVENT_AXIS_RELATIVE_X
const int EVENT_AXIS_RELATIVE_Y = 28; // AMOTION_EVENT_AXIS_RELATIVE_Y
class RawMouseConnectionNotificationsAndroid
: public AZ::EBusTraits
{
public:
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
virtual ~RawMouseConnectionNotificationsAndroid() = default;
virtual void OnMouseConnected() = 0;
virtual void OnMouseDisconnected() = 0;
};
using RawMouseConnectionNotificationsBusAndroid = AZ::EBus<RawMouseConnectionNotificationsAndroid>;
void JNI_OnMouseConnected(JNIEnv* jniEnv, jobject objectRef)
{
RawMouseConnectionNotificationsBusAndroid::Broadcast(&RawMouseConnectionNotificationsAndroid::OnMouseConnected);
}
void JNI_OnMouseDisconnected(JNIEnv* jniEnv, jobject objectRef)
{
RawMouseConnectionNotificationsBusAndroid::Broadcast(&RawMouseConnectionNotificationsAndroid::OnMouseDisconnected);
}
}
namespace AzFramework
{
//! Platform specific implementation for Android physical mouse input devices. This
//! includes devices connected through USB or Bluetooth.
class InputDeviceMouseAndroid
: public InputDeviceMouse::Implementation
, public RawInputNotificationBusAndroid::Handler
, public RawMouseConnectionNotificationsBusAndroid::Handler
{
public:
AZ_CLASS_ALLOCATOR(InputDeviceMouseAndroid, AZ::SystemAllocator, 0);
InputDeviceMouseAndroid(InputDeviceMouse& inputDevice);
~InputDeviceMouseAndroid() override;
//! \ref AzFramework::RawInputNotificationsAndroid::OnRawInputEvent
void OnRawInputEvent(const AInputEvent* rawInputEvent) override;
private:
//! \ref AzFramework::InputDeviceMouse::Implementation::IsConnected
bool IsConnected() const override { return m_isConnected; }
//! \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;
//!@{
//! \brief Connection state callbacks from java
void OnMouseConnected() override { m_isConnected = true; }
void OnMouseDisconnected() override { m_isConnected = false; }
//!@}
//! Special case processing of key events for certain devices that treat some mouse buttons
//! as simulated key events
void HandleKeyEvent(const AInputEvent* rawInputEvent);
//! Helper for processing the common mouse button event processing
void ProcessMouseButtonEvent(int buttonState, bool isPressed);
AZStd::unique_ptr<AZ::Android::JNI::Object> m_mouseDevice; //!< Java interface for getting connection state info
AZ::Vector2 m_mouseWindowPixelPosition; //!< Cached location of the mouse, in pixel window space
int m_mouseButtonState; //!< Cached flags of which mouse buttons are currently active
volatile bool m_isConnected; //!< Cached connection state, ie. at least 1 mouse is connected
};
InputDeviceMouseAndroid::InputDeviceMouseAndroid(InputDeviceMouse& inputDevice)
: InputDeviceMouse::Implementation(inputDevice)
, m_mouseWindowPixelPosition(AZ::Vector2::CreateZero())
, m_mouseButtonState(0)
, m_isConnected(false)
{
// Initialize the mouse device handler
m_mouseDevice.reset(aznew AZ::Android::JNI::Object("com/amazon/lumberyard/input/MouseDevice"));
m_mouseDevice->RegisterMethod("IsConnected", "()Z");
m_mouseDevice->RegisterNativeMethods(
{
{ "OnMouseConnected", "()V", (void*)JNI_OnMouseConnected },
{ "OnMouseDisconnected", "()V", (void*)JNI_OnMouseDisconnected }
});
// create the java instance
bool ret = m_mouseDevice->CreateInstance("(Landroid/app/Activity;)V", AZ::Android::Utils::GetActivityRef());
AZ_Assert(ret, "Failed to create the MouseDevice Java instance.");
if (ret)
{
m_isConnected = m_mouseDevice->InvokeBooleanMethod("IsConnected");
}
RawInputNotificationBusAndroid::Handler::BusConnect();
RawMouseConnectionNotificationsBusAndroid::Handler::BusConnect();
}
InputDeviceMouseAndroid::~InputDeviceMouseAndroid()
{
RawMouseConnectionNotificationsBusAndroid::Handler::BusDisconnect();
RawInputNotificationBusAndroid::Handler::BusDisconnect();
}
void InputDeviceMouseAndroid::OnRawInputEvent(const AInputEvent* rawInputEvent)
{
if (!m_isConnected)
{
return;
}
// special case for some devices that treat the right click as a back button event
const int eventType = AInputEvent_getType(rawInputEvent);
if (eventType == AINPUT_EVENT_TYPE_KEY)
{
HandleKeyEvent(rawInputEvent);
return;
}
// Get the action code and pointer index
const int action = AMotionEvent_getAction(rawInputEvent);
const int actionCode = (action & AMOTION_EVENT_ACTION_MASK);
const int pointerIndex = (action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
// ensure we are only dispatching pure mouse events and not simulated ones
const int toolType = AMotionEvent_getToolType(rawInputEvent, pointerIndex);
if (toolType != AMOTION_EVENT_TOOL_TYPE_MOUSE)
{
return;
}
const AZ::Android::ApiLevel sdkVersion = AZ::Android::GetRuntimeApiLevel();
const int buttonState = AMotionEvent_getButtonState(rawInputEvent);
switch (actionCode)
{
case AMOTION_EVENT_ACTION_POINTER_DOWN:
// guard against duplicate events when running on API 23 or above
if (sdkVersion >= AZ::Android::ApiLevel::Marshmallow)
{
break;
}
// fallthrough
case EVENT_ACTION_BUTTON_PRESS:
{
// extract the mouse button that was added to the flags
int newButtonState = buttonState & ~m_mouseButtonState;
ProcessMouseButtonEvent(newButtonState, true);
m_mouseButtonState = buttonState;
break;
}
case AMOTION_EVENT_ACTION_POINTER_UP:
// guard against duplicate events when running on API 23 or above
if (sdkVersion >= AZ::Android::ApiLevel::Marshmallow)
{
break;
}
// fallthrough
case EVENT_ACTION_BUTTON_RELEASE:
{
// extract the mouse button that was removed from the flags
int removedButtonState = m_mouseButtonState & ~buttonState;
ProcessMouseButtonEvent(removedButtonState, false);
m_mouseButtonState = buttonState;
break;
}
case AMOTION_EVENT_ACTION_SCROLL:
{
const float verticalScroll = AMotionEvent_getAxisValue(rawInputEvent, AMOTION_EVENT_AXIS_VSCROLL, pointerIndex);
QueueRawMovementEvent(InputDeviceMouse::Movement::Z, verticalScroll);
break;
}
case AMOTION_EVENT_ACTION_MOVE:
case AMOTION_EVENT_ACTION_HOVER_MOVE:
{
const AZ::Vector2 mousePostion {
AMotionEvent_getX(rawInputEvent, pointerIndex),
AMotionEvent_getY(rawInputEvent, pointerIndex)
};
AZ::Vector2 mouseDeltaPostion(AZ::Vector2::CreateZero());
if (sdkVersion >= AZ::Android::ApiLevel::Nougat)
{
mouseDeltaPostion.SetX(AMotionEvent_getAxisValue(rawInputEvent, EVENT_AXIS_RELATIVE_X, pointerIndex));
mouseDeltaPostion.SetY(AMotionEvent_getAxisValue(rawInputEvent, EVENT_AXIS_RELATIVE_Y, pointerIndex));
}
else
{
mouseDeltaPostion = mousePostion - m_mouseWindowPixelPosition;
}
QueueRawMovementEvent(InputDeviceMouse::Movement::X, mouseDeltaPostion.GetX());
QueueRawMovementEvent(InputDeviceMouse::Movement::Y, mouseDeltaPostion.GetY());
m_mouseWindowPixelPosition = mousePostion;
break;
}
default:
// do nothing
break;
}
}
void InputDeviceMouseAndroid::SetSystemCursorState(SystemCursorState systemCursorState)
{
if (m_isConnected)
{
AZ_WarningOnce("InputDeviceMouseAndroid", false, "Calls to SetSystemCursorState are unsupported on Android.");
}
}
SystemCursorState InputDeviceMouseAndroid::GetSystemCursorState() const
{
// Returning the correct state here is a bit tricky because the system will auto show and hide
// the cursor based on usage of the physical mouse. The application has no control over
// the visibily of the cursor, nor do we have control of constraining the cursor to inside the
// applicaiton window. Always returning UnconstrainedAndVisible is going to be the closest
// to accurate value we can send when a physical mouse is connected.
return (m_isConnected ? SystemCursorState::UnconstrainedAndVisible : SystemCursorState::Unknown);
}
void InputDeviceMouseAndroid::SetSystemCursorPositionNormalized(AZ::Vector2 positionNormalized)
{
if (m_isConnected)
{
AZ_WarningOnce("InputDeviceMouseAndroid", false, "Calls to SetSystemCursorPositionNormalized are unsupported on Android.");
}
}
AZ::Vector2 InputDeviceMouseAndroid::GetSystemCursorPositionNormalized() const
{
int windowWidth = 0;
int windowHeight = 0;
if (!AZ::Android::Utils::GetWindowSize(windowWidth, windowHeight))
{
AZ_ErrorOnce("InputDeviceMouseAndroid", false, "Unable to get the window size, the mouse location will NOT be normalized.");
windowWidth = 1;
windowHeight = 1;
}
return AZ::Vector2(m_mouseWindowPixelPosition.GetX() / static_cast<float>(windowWidth),
m_mouseWindowPixelPosition.GetY() / static_cast<float>(windowHeight));
}
void InputDeviceMouseAndroid::TickInputDevice()
{
if (m_isConnected)
{
ProcessRawEventQueues();
}
}
void InputDeviceMouseAndroid::HandleKeyEvent(const AInputEvent* rawInputEvent)
{
const int inputSource = AInputEvent_getSource(rawInputEvent);
if ((inputSource & AINPUT_SOURCE_MOUSE) == AINPUT_SOURCE_MOUSE)
{
const int keyCode = AKeyEvent_getKeyCode(rawInputEvent);
if (keyCode == AKEYCODE_BACK)
{
const int keyAction = AKeyEvent_getAction(rawInputEvent);
switch (keyAction)
{
case AKEY_EVENT_ACTION_DOWN:
// guard against duplicate events when holding down the button
if (AKeyEvent_getRepeatCount(rawInputEvent) == 0)
{
QueueRawButtonEvent(InputDeviceMouse::Button::Right, true);
}
break;
case AKEY_EVENT_ACTION_UP:
QueueRawButtonEvent(InputDeviceMouse::Button::Right, false);
break;
default:
// do nothing
break;
}
}
}
}
void InputDeviceMouseAndroid::ProcessMouseButtonEvent(int buttonState, bool isPressed)
{
if ((buttonState & AMOTION_EVENT_BUTTON_PRIMARY) == AMOTION_EVENT_BUTTON_PRIMARY)
{
QueueRawButtonEvent(InputDeviceMouse::Button::Left, isPressed);
}
else if ((buttonState & AMOTION_EVENT_BUTTON_SECONDARY) == AMOTION_EVENT_BUTTON_SECONDARY)
{
QueueRawButtonEvent(InputDeviceMouse::Button::Right, isPressed);
}
else if ((buttonState & AMOTION_EVENT_BUTTON_TERTIARY) == AMOTION_EVENT_BUTTON_TERTIARY)
{
QueueRawButtonEvent(InputDeviceMouse::Button::Middle, isPressed);
}
}
InputDeviceMouse::Implementation* InputDeviceMouse::Implementation::Create(InputDeviceMouse& inputDevice)
{
return aznew InputDeviceMouseAndroid(inputDevice);
}
} // namespace AzFramework
@@ -0,0 +1,231 @@
/*
* 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/Devices/Touch/InputDeviceTouch.h>
#include <AzFramework/Input/Buses/Notifications/RawInputNotificationBus_Platform.h>
#include <AzCore/Android/Utils.h>
#include <AzCore/std/parallel/mutex.h>
#include <android/input.h>
#include <android/keycodes.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace AzFramework
{
////////////////////////////////////////////////////////////////////////////////////////////////
//! Platform specific implementation for android touch input devices
class InputDeviceTouchAndroid : public InputDeviceTouch::Implementation
, public RawInputNotificationBusAndroid::Handler
{
public:
////////////////////////////////////////////////////////////////////////////////////////////
// Allocator
AZ_CLASS_ALLOCATOR(InputDeviceTouchAndroid, AZ::SystemAllocator, 0);
////////////////////////////////////////////////////////////////////////////////////////////
//! Constructor
//! \param[in] inputDevice Reference to the input device being implemented
InputDeviceTouchAndroid(InputDeviceTouch& inputDevice);
////////////////////////////////////////////////////////////////////////////////////////////
//! Destructor
~InputDeviceTouchAndroid() override;
private:
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceTouch::Implementation::IsConnected
bool IsConnected() const override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceTouch::Implementation::TickInputDevice
void TickInputDevice() override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::RawInputNotificationsAndroid::OnRawInputEvent
void OnRawInputEvent(const AInputEvent* rawInputEvent) override;
////////////////////////////////////////////////////////////////////////////////////////////
//! Convenience function to process raw touch events
//! \param[in] rawInputEvent The raw input event data
//! \param[in] pointerIndex The index of the touch event
//! \param[in] rawTouchState The state of the touch event
void OnRawTouchEvent(const AInputEvent* rawInputEvent,
int pointerIndex,
RawTouchEvent::State rawTouchState);
////////////////////////////////////////////////////////////////////////////////////////////
//! Raw input events on android can (and likely will) be dispatched from a thread other than
//! main, so we can't immediately call InputDeviceTouch::Implementation::QueueRawTouchEvent.
//! Instead, we'll store them in m_threadAwareRawTouchEvents then process in TickInputDevice
//! on the main thread, ensuring all access is locked using m_threadAwareRawTouchEventsMutex.
///@{
AZStd::vector<RawTouchEvent> m_threadAwareRawTouchEvents;
AZStd::mutex m_threadAwareRawTouchEventsMutex;
///@}
};
////////////////////////////////////////////////////////////////////////////////////////////////
InputDeviceTouch::Implementation* InputDeviceTouch::Implementation::Create(InputDeviceTouch& inputDevice)
{
return aznew InputDeviceTouchAndroid(inputDevice);
}
////////////////////////////////////////////////////////////////////////////////////////////////
InputDeviceTouchAndroid::InputDeviceTouchAndroid(InputDeviceTouch& inputDevice)
: InputDeviceTouch::Implementation(inputDevice)
, m_threadAwareRawTouchEvents()
, m_threadAwareRawTouchEventsMutex()
{
RawInputNotificationBusAndroid::Handler::BusConnect();
}
////////////////////////////////////////////////////////////////////////////////////////////////
InputDeviceTouchAndroid::~InputDeviceTouchAndroid()
{
RawInputNotificationBusAndroid::Handler::BusDisconnect();
}
////////////////////////////////////////////////////////////////////////////////////////////////
bool InputDeviceTouchAndroid::IsConnected() const
{
// Touch input is always available on android
return true;
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceTouchAndroid::TickInputDevice()
{
// The input event loop is pumped by another thread on android so all raw input events 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.
// Because m_threadAwareRawTouchEvents is updated from another thread, but also needs to be
// processed and cleared in this function, swapping it with an empty vector kills two birds
// with one stone in a very efficient and elegant manner (kudos to scottr for the idea).
AZStd::vector<RawTouchEvent> rawTouchEvents;
{
AZStd::lock_guard<AZStd::mutex> lock(m_threadAwareRawTouchEventsMutex);
rawTouchEvents.swap(m_threadAwareRawTouchEvents);
}
// Queue the raw touch events that were received over the last frame
for (const RawTouchEvent& rawTouchEvent : rawTouchEvents)
{
QueueRawTouchEvent(rawTouchEvent);
}
// Process the raw event queues once each frame
ProcessRawEventQueues();
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceTouchAndroid::OnRawInputEvent(const AInputEvent* rawInputEvent)
{
// Reference: https://developer.android.com/ndk/reference/group___input.html
// Discard non-touch events
const int eventType = AInputEvent_getType(rawInputEvent);
if (eventType != AINPUT_EVENT_TYPE_MOTION)
{
return;
}
// Get the action code and pointer index
const int action = AMotionEvent_getAction(rawInputEvent);
const int actionCode = (action & AMOTION_EVENT_ACTION_MASK);
const int pointerIndex = (action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
switch (actionCode)
{
case AMOTION_EVENT_ACTION_DOWN:
case AMOTION_EVENT_ACTION_POINTER_DOWN:
{
OnRawTouchEvent(rawInputEvent, pointerIndex, RawTouchEvent::State::Began);
}
break;
case AMOTION_EVENT_ACTION_MOVE:
{
// Android doesn't send individual move events for each separate touch like it does
// for down and up events, instead sending them all as part of the same input event.
const size_t pointerCount = AMotionEvent_getPointerCount(rawInputEvent);
for (int i = 0; i < pointerCount; ++i)
{
OnRawTouchEvent(rawInputEvent, i, RawTouchEvent::State::Moved);
}
}
break;
case AMOTION_EVENT_ACTION_UP:
case AMOTION_EVENT_ACTION_POINTER_UP:
case AMOTION_EVENT_ACTION_CANCEL:
{
OnRawTouchEvent(rawInputEvent, pointerIndex, RawTouchEvent::State::Ended);
}
break;
default:
{
return;
}
break;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceTouchAndroid::OnRawTouchEvent(const AInputEvent* rawInputEvent,
int pointerIndex,
RawTouchEvent::State rawTouchState)
{
// Allow simulated touch events (generated by "adb shell monkey") or real finger touch events.
// (but not stylus, mouse or eraser)
const int toolType = AMotionEvent_getToolType(rawInputEvent, pointerIndex);
if (toolType != AMOTION_EVENT_TOOL_TYPE_FINGER && toolType != AMOTION_EVENT_TOOL_TYPE_UNKNOWN)
{
return;
}
// Get the rest of the touch event data
const float x = AMotionEvent_getX(rawInputEvent, pointerIndex);
const float y = AMotionEvent_getY(rawInputEvent, pointerIndex);
float pressure = AMotionEvent_getPressure(rawInputEvent, pointerIndex);
const int pointerId = AMotionEvent_getPointerId(rawInputEvent, pointerIndex);
// The simulated events always have a pressure of zero. An analog input channel is only active
// if the value is != 0.0f. Touch input was implemented as an analog input channel because both
// iOS and Android report touch input with a pressure value, and for anything that doesn't report
// a pressure value you have to explicitly set it as 1.0f (or really anything != 0.0f) for 'active'
// and 0.0f for not active.
if (toolType == AMOTION_EVENT_TOOL_TYPE_UNKNOWN && pressure == 0.0f)
{
pressure = 1.0f;
}
// normalize the touch location
int windowWidth = 0;
int windowHeight = 0;
if (!AZ::Android::Utils::GetWindowSize(windowWidth, windowHeight))
{
AZ_ErrorOnce("InputDeviceTouchAndroid", false, "Unable to get the window size, touch input may not behave correctly.");
windowWidth = 1;
windowHeight = 1;
}
// Push the raw touch event onto the thread safe queue for processing in TickInputDevice
const RawTouchEvent rawTouchEvent(x / static_cast<float>(windowWidth),
y / static_cast<float>(windowHeight),
rawTouchState == RawTouchEvent::State::Ended ? 0.0f : pressure,
pointerId,
rawTouchState);
AZStd::lock_guard<AZStd::mutex> lock(m_threadAwareRawTouchEventsMutex);
m_threadAwareRawTouchEvents.push_back(rawTouchEvent);
}
}
@@ -0,0 +1,296 @@
/*
* 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/Devices/VirtualKeyboard/InputDeviceVirtualKeyboard.h>
#include <AzFramework/Input/Buses/Notifications/RawInputNotificationBus_Platform.h>
#include <AzCore/std/parallel/mutex.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/Android/JNI/Object.h>
#include <AzCore/Android/Utils.h>
#include <android/input.h>
#include <android/keycodes.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace AzFramework
{
////////////////////////////////////////////////////////////////////////////////////////////////////
void JNI_OnRawTextEvent(JNIEnv* jniEnv, jobject objectRef, jstring stringUTF16)
{
const char* charsModifiedUTF8 = jniEnv->GetStringUTFChars(stringUTF16, nullptr);
RawInputNotificationBusAndroid::Broadcast(&RawInputNotificationsAndroid::OnRawInputTextEvent,
charsModifiedUTF8);
jniEnv->ReleaseStringUTFChars(stringUTF16, charsModifiedUTF8);
}
////////////////////////////////////////////////////////////////////////////////////////////////
//! Platform specific implementation for Android virtual keyboard input devices. This input device
//! is responsible for sending text events only, the physical keyboard is the one responsible
//! for sending key events. Should this behaviour need to be changed, LY-69260 will correct it
class InputDeviceVirtualKeyboardAndroid : public InputDeviceVirtualKeyboard::Implementation
, public RawInputNotificationBusAndroid::Handler
{
public:
////////////////////////////////////////////////////////////////////////////////////////////
// Allocator
AZ_CLASS_ALLOCATOR(InputDeviceVirtualKeyboardAndroid, AZ::SystemAllocator, 0);
////////////////////////////////////////////////////////////////////////////////////////////
//! Constructor
//! \param[in] inputDevice Reference to the input device being implemented
InputDeviceVirtualKeyboardAndroid(InputDeviceVirtualKeyboard& inputDevice);
////////////////////////////////////////////////////////////////////////////////////////////
//! Destructor
~InputDeviceVirtualKeyboardAndroid() override;
private:
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceVirtualKeyboard::Implementation::IsConnected
bool IsConnected() const override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceVirtualKeyboard::Implementation::HasTextEntryStarted
bool HasTextEntryStarted() const override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceVirtualKeyboard::Implementation::TextEntryStart
void TextEntryStart(const InputTextEntryRequests::VirtualKeyboardOptions& options) override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceVirtualKeyboard::Implementation::TextEntryStop
void TextEntryStop() override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceVirtualKeyboard::Implementation::TickInputDevice
void TickInputDevice() override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::RawInputNotificationsAndroid::OnRawInputEvent
void OnRawInputEvent(const AInputEvent* rawInputEvent) override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::RawInputNotificationsAndroid::OnRawInputTextEvent
void OnRawInputTextEvent(const char* charsModifiedUTF8) override;
////////////////////////////////////////////////////////////////////////////////////////////
//! Raw input events on android can (and likely will) be dispatched from a thread other than
//! main, and InputDeviceVirtualKeyboard::Implementation::QueueRawCommandEvent is not thread
//! safe. So we'll store them in m_threadAwareRawCommandEvents to process in TickInputDevice
//! on the main thread, ensuring all access is locked by m_threadAwareRawCommandEventsMutex.
///@{
AZStd::vector<InputChannelId> m_threadAwareRawCommandEvents;
AZStd::mutex m_threadAwareRawCommandEventsMutex;
///@}
////////////////////////////////////////////////////////////////////////////////////////////
//! Raw input events on android can (and likely will) be dispatched from a thread other than
//! main, and InputDeviceVirtualKeyboard::Implementation::QueueRawTextEvent is not thread
//! safe. So we'll store them in m_threadAwareRawTextEvents to process in TickInputDevice
//! on the main thread, ensuring all access is locked by m_threadAwareRawTextEventsMutex.
///@{
AZStd::vector<AZStd::string> m_threadAwareRawTextEvents;
AZStd::mutex m_threadAwareRawTextEventsMutex;
///@}
////////////////////////////////////////////////////////////////////////////////////////////
//! Unique pointer to the keyboard handler JNI object
AZStd::unique_ptr<AZ::Android::JNI::Object> m_keyboardHandler;
};
////////////////////////////////////////////////////////////////////////////////////////////////
InputDeviceVirtualKeyboard::Implementation* InputDeviceVirtualKeyboard::Implementation::Create(
InputDeviceVirtualKeyboard& inputDevice)
{
return aznew InputDeviceVirtualKeyboardAndroid(inputDevice);
}
////////////////////////////////////////////////////////////////////////////////////////////////
InputDeviceVirtualKeyboardAndroid::InputDeviceVirtualKeyboardAndroid(
InputDeviceVirtualKeyboard& inputDevice)
: InputDeviceVirtualKeyboard::Implementation(inputDevice)
{
// Initialize the keyboard handler
m_keyboardHandler.reset(aznew AZ::Android::JNI::Object("com/amazon/lumberyard/input/KeyboardHandler"));
m_keyboardHandler->RegisterMethod("ShowTextInput", "()V");
m_keyboardHandler->RegisterMethod("HideTextInput", "()V");
m_keyboardHandler->RegisterMethod("IsShowing", "()Z");
m_keyboardHandler->RegisterNativeMethods(
{
{ "SendUnicodeText", "(Ljava/lang/String;)V", (void*)JNI_OnRawTextEvent }
});
// create the java instance
bool ret = m_keyboardHandler->CreateInstance("(Landroid/app/Activity;)V", AZ::Android::Utils::GetActivityRef());
AZ_Assert(ret, "Failed to create the KeyboardHandler Java instance.");
// Connect to the raw input notifications bus
RawInputNotificationBusAndroid::Handler::BusConnect();
}
////////////////////////////////////////////////////////////////////////////////////////////////
InputDeviceVirtualKeyboardAndroid::~InputDeviceVirtualKeyboardAndroid()
{
// Disconnect from the raw input notifications bus
RawInputNotificationBusAndroid::Handler::BusDisconnect();
}
////////////////////////////////////////////////////////////////////////////////////////////////
bool InputDeviceVirtualKeyboardAndroid::IsConnected() const
{
// Virtual keyboard input is always available
return true;
}
////////////////////////////////////////////////////////////////////////////////////////////////
bool InputDeviceVirtualKeyboardAndroid::HasTextEntryStarted() const
{
return m_keyboardHandler ? m_keyboardHandler->InvokeBooleanMethod("IsShowing") == JNI_TRUE : false;
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceVirtualKeyboardAndroid::TextEntryStart(const InputTextEntryRequests::VirtualKeyboardOptions&)
{
if (m_keyboardHandler && !m_keyboardHandler->InvokeBooleanMethod("IsShowing"))
{
m_keyboardHandler->InvokeVoidMethod("ShowTextInput");
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceVirtualKeyboardAndroid::TextEntryStop()
{
if (m_keyboardHandler && m_keyboardHandler->InvokeBooleanMethod("IsShowing"))
{
m_keyboardHandler->InvokeVoidMethod("HideTextInput");
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceVirtualKeyboardAndroid::TickInputDevice()
{
// The input event loop is pumped by another thread on android so all raw input events 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.
// Because m_threadAwareRawCommandEvents is updated from another thread but also needs to be
// processed and cleared in this function, swapping it with an empty vector kills two birds
// with one stone in a very efficient and elegant manner (kudos to scottr for the idea).
AZStd::vector<InputChannelId> rawCommandEvents;
{
AZStd::lock_guard<AZStd::mutex> lock(m_threadAwareRawCommandEventsMutex);
rawCommandEvents.swap(m_threadAwareRawCommandEvents);
}
// Queue the raw command events that were received over the last frame
for (const InputChannelId& rawCommandEvent : rawCommandEvents)
{
QueueRawCommandEvent(rawCommandEvent);
}
// Because m_threadAwareRawTextEvents is updated from another thread, but also needs to be
// processed and cleared in this function, swapping it with an empty vector kills two birds
// with one stone in a very efficient and elegant manner (kudos to scottr for the idea).
AZStd::vector<AZStd::string> rawTextEvents;
{
AZStd::lock_guard<AZStd::mutex> lock(m_threadAwareRawTextEventsMutex);
rawTextEvents.swap(m_threadAwareRawTextEvents);
}
// Queue the raw text events that were received over the last frame
for (const AZStd::string& rawTextEvent : rawTextEvents)
{
QueueRawTextEvent(rawTextEvent);
}
// Process the raw event queues once each frame
ProcessRawEventQueues();
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceVirtualKeyboardAndroid::OnRawInputEvent(const AInputEvent* rawInputEvent)
{
// only care about key events
if (AInputEvent_getType(rawInputEvent) != AINPUT_EVENT_TYPE_KEY)
{
return;
}
// We only care about key down actions
if (AKeyEvent_getAction(rawInputEvent) != AKEY_EVENT_ACTION_DOWN)
{
return;
}
const int keyCode = AKeyEvent_getKeyCode(rawInputEvent);
// always send the back event, but only if it's coming from the system
if (keyCode == AKEYCODE_BACK)
{
const int eventFlags = AKeyEvent_getFlags(rawInputEvent);
if ((eventFlags & AKEY_EVENT_FLAG_FROM_SYSTEM) == AKEY_EVENT_FLAG_FROM_SYSTEM)
{
AZStd::lock_guard<AZStd::mutex> lock(m_threadAwareRawCommandEventsMutex);
m_threadAwareRawCommandEvents.push_back(AzFramework::InputDeviceVirtualKeyboard::Command::NavigationBack);
}
return;
}
// early out on the rest of the event processing if the keyboard isn't active
if (!HasTextEntryStarted())
{
return;
}
switch (keyCode)
{
case AKEYCODE_CLEAR:
{
AZStd::lock_guard<AZStd::mutex> lock(m_threadAwareRawCommandEventsMutex);
m_threadAwareRawCommandEvents.push_back(AzFramework::InputDeviceVirtualKeyboard::Command::EditClear);
}
break;
case AKEYCODE_ENTER:
{
AZStd::lock_guard<AZStd::mutex> lock(m_threadAwareRawCommandEventsMutex);
m_threadAwareRawCommandEvents.push_back(AzFramework::InputDeviceVirtualKeyboard::Command::EditEnter);
}
break;
case AKEYCODE_DEL:
{
AZStd::lock_guard<AZStd::mutex> lock(m_threadAwareRawTextEventsMutex);
m_threadAwareRawTextEvents.push_back(AZStd::string("\b"));
}
break;
case AKEYCODE_SPACE:
{
AZStd::lock_guard<AZStd::mutex> lock(m_threadAwareRawTextEventsMutex);
m_threadAwareRawTextEvents.push_back(AZStd::string(" "));
}
break;
default:
{
// Do nothing
}
break;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceVirtualKeyboardAndroid::OnRawInputTextEvent(const char* charsModifiedUTF8)
{
AZStd::lock_guard<AZStd::mutex> lock(m_threadAwareRawTextEventsMutex);
m_threadAwareRawTextEvents.push_back(AZStd::string(charsModifiedUTF8));
}
} // 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,62 @@
/*
* 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/Windowing/NativeWindow.h>
#include <AzCore/Android/Utils.h>
#include <android/native_window.h>
namespace AzFramework
{
class NativeWindowImpl_Android final
: public NativeWindow::Implementation
{
public:
AZ_CLASS_ALLOCATOR(NativeWindowImpl_Android, AZ::SystemAllocator, 0);
NativeWindowImpl_Android() = default;
~NativeWindowImpl_Android() override = default;
// NativeWindow::Implementation overrides...
void InitWindow(const AZStd::string& title,
const WindowGeometry& geometry,
const WindowStyleMasks& styleMasks) override;
NativeWindowHandle GetWindowHandle() const override;
private:
ANativeWindow* m_nativeWindow = nullptr;
};
NativeWindow::Implementation* NativeWindow::Implementation::Create()
{
return aznew NativeWindowImpl_Android();
}
void NativeWindowImpl_Android::InitWindow([[maybe_unused]]const AZStd::string& title,
const WindowGeometry& geometry,
[[maybe_unused]]const WindowStyleMasks& styleMasks)
{
m_nativeWindow = AZ::Android::Utils::GetWindow();
m_width = geometry.m_width;
m_height = geometry.m_height;
if (m_nativeWindow)
{
ANativeWindow_setBuffersGeometry(m_nativeWindow, m_width, m_height, ANativeWindow_getFormat(m_nativeWindow));
}
}
NativeWindowHandle NativeWindowImpl_Android::GetWindowHandle() const
{
return reinterpret_cast<NativeWindowHandle>(m_nativeWindow);
}
} // 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,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.
#
set(FILES
AzFramework/AzFramework_Traits_Platform.h
AzFramework/AzFramework_Traits_Android.h
AzFramework/API/ApplicationAPI_Platform.h
AzFramework/API/ApplicationAPI_Android.h
AzFramework/Application/Application_Android.cpp
../Common/Unimplemented/AzFramework/Asset/AssetSystemComponentHelper_Unimplemented.cpp
AzFramework/IO/LocalFileIO_Android.cpp
../Common/Default/AzFramework/Network/AssetProcessorConnection_Default.cpp
../Common/Unimplemented/AzFramework/StreamingInstall/StreamingInstall_Unimplemented.cpp
../Common/Default/AzFramework/TargetManagement/TargetManagementComponent_Default.cpp
AzFramework/Windowing/NativeWindow_Android.cpp
AzFramework/Input/Buses/Notifications/RawInputNotificationBus_Platform.h
AzFramework/Input/Buses/Notifications/RawInputNotificationBus_Android.h
AzFramework/Input/Devices/Gamepad/InputDeviceGamepad_Android.cpp
AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard_Android.cpp
AzFramework/Input/Devices/Motion/InputDeviceMotion_Android.cpp
AzFramework/Input/Devices/Mouse/InputDeviceMouse_Android.cpp
AzFramework/Input/Devices/Touch/InputDeviceTouch_Android.cpp
AzFramework/Input/User/LocalUserId_Platform.h
../Common/Default/AzFramework/Input/User/LocalUserId_Default.h
AzFramework/Input/Devices/VirtualKeyboard/InputDeviceVirtualKeyboard_Android.cpp
AzFramework/Archive/ArchiveVars_Platform.h
AzFramework/Archive/ArchiveVars_Android.h
)
@@ -0,0 +1,332 @@
/*
* 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/Devices/Gamepad/InputDeviceGamepad.h>
#include <GameController/GameController.h>
#include <AzCore/Debug/Trace.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace
{
using namespace AzFramework;
////////////////////////////////////////////////////////////////////////////////////////////////
//! Digital button bitmasks
const AZ::u32 DIGITAL_BUTTON_MASK_DU = 0x0001;
const AZ::u32 DIGITAL_BUTTON_MASK_DD = 0x0002;
const AZ::u32 DIGITAL_BUTTON_MASK_DL = 0x0004;
const AZ::u32 DIGITAL_BUTTON_MASK_DR = 0x0008;
const AZ::u32 DIGITAL_BUTTON_MASK_L1 = 0x0010;
const AZ::u32 DIGITAL_BUTTON_MASK_R1 = 0x0020;
const AZ::u32 DIGITAL_BUTTON_MASK_L3 = 0x0040;
const AZ::u32 DIGITAL_BUTTON_MASK_R3 = 0x0080;
const AZ::u32 DIGITAL_BUTTON_MASK_A = 0x0100;
const AZ::u32 DIGITAL_BUTTON_MASK_B = 0x0200;
const AZ::u32 DIGITAL_BUTTON_MASK_X = 0x0400;
const AZ::u32 DIGITAL_BUTTON_MASK_Y = 0x0800;
const AZ::u32 DIGITAL_BUTTON_MASK_PAUSE = 0x1000;
const AZ::u32 DIGITAL_BUTTON_MASK_SELECT = 0x2000;
////////////////////////////////////////////////////////////////////////////////////////////////
//! Map of digital button ids keyed by their button bitmask
const AZStd::unordered_map<AZ::u32, const InputChannelId*> GetDigitalButtonIdByBitMaskMap()
{
const AZStd::unordered_map<AZ::u32, const InputChannelId*> map =
{
{ DIGITAL_BUTTON_MASK_DU, &InputDeviceGamepad::Button::DU },
{ DIGITAL_BUTTON_MASK_DD, &InputDeviceGamepad::Button::DD },
{ DIGITAL_BUTTON_MASK_DL, &InputDeviceGamepad::Button::DL },
{ DIGITAL_BUTTON_MASK_DR, &InputDeviceGamepad::Button::DR },
{ DIGITAL_BUTTON_MASK_L1, &InputDeviceGamepad::Button::L1 },
{ DIGITAL_BUTTON_MASK_R1, &InputDeviceGamepad::Button::R1 },
{ DIGITAL_BUTTON_MASK_L3, &InputDeviceGamepad::Button::L3 },
{ DIGITAL_BUTTON_MASK_R3, &InputDeviceGamepad::Button::R3 },
{ DIGITAL_BUTTON_MASK_A, &InputDeviceGamepad::Button::A },
{ DIGITAL_BUTTON_MASK_B, &InputDeviceGamepad::Button::B },
{ DIGITAL_BUTTON_MASK_X, &InputDeviceGamepad::Button::X },
{ DIGITAL_BUTTON_MASK_Y, &InputDeviceGamepad::Button::Y },
{ DIGITAL_BUTTON_MASK_PAUSE, &InputDeviceGamepad::Button::Start },
{ DIGITAL_BUTTON_MASK_SELECT, &InputDeviceGamepad::Button::Select }
};
return map;
}
} // namespace
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace AzFramework
{
////////////////////////////////////////////////////////////////////////////////////////////////
//! Platform specific implementation for apple game-pad input devices
class InputDeviceGamepadApple : public InputDeviceGamepad::Implementation
{
public:
////////////////////////////////////////////////////////////////////////////////////////////
// Allocator
AZ_CLASS_ALLOCATOR(InputDeviceGamepadApple, AZ::SystemAllocator, 0);
////////////////////////////////////////////////////////////////////////////////////////////
//! Constructor
//! \param[in] inputDevice Reference to the input device being implemented
InputDeviceGamepadApple(InputDeviceGamepad& inputDevice);
////////////////////////////////////////////////////////////////////////////////////////////
//! Destructor
~InputDeviceGamepadApple() 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;
////////////////////////////////////////////////////////////////////////////////////////////
// Variables
RawGamepadState m_rawGamepadState; //!< The last known raw game-pad state
GCController* m_controller; //!< The currently assigned controller
bool m_wasPausedHandlerCalled; //!< Was the controller paused handler called?
};
////////////////////////////////////////////////////////////////////////////////////////////////
AZ::u32 InputDeviceGamepad::GetMaxSupportedGamepads()
{
return GCControllerPlayerIndex4 + 1;
}
////////////////////////////////////////////////////////////////////////////////////////////////
InputDeviceGamepad::Implementation* InputDeviceGamepad::Implementation::Create(
InputDeviceGamepad& inputDevice)
{
return aznew InputDeviceGamepadApple(inputDevice);
}
////////////////////////////////////////////////////////////////////////////////////////////////
InputDeviceGamepadApple::InputDeviceGamepadApple(InputDeviceGamepad& inputDevice)
: InputDeviceGamepad::Implementation(inputDevice)
, m_rawGamepadState(GetDigitalButtonIdByBitMaskMap())
, m_controller(nullptr)
, m_wasPausedHandlerCalled(false)
{
AZ_Assert(inputDevice.GetInputDeviceId().GetIndex() < InputDeviceGamepad::GetMaxSupportedGamepads(),
"Creating InputDeviceGamepadApple with index %d that is greater than the max supported by the game controller framework: %d",
inputDevice.GetInputDeviceId().GetIndex(), InputDeviceGamepad::GetMaxSupportedGamepads());
}
////////////////////////////////////////////////////////////////////////////////////////////////
InputDeviceGamepadApple::~InputDeviceGamepadApple()
{
}
////////////////////////////////////////////////////////////////////////////////////////////////
bool InputDeviceGamepadApple::IsConnected() const
{
return m_controller != nullptr;
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceGamepadApple::SetVibration(float leftMotorSpeedNormalized,
float rightMotorSpeedNormalized)
{
// The apple game controller framework does not (yet?) support force-feedback
}
////////////////////////////////////////////////////////////////////////////////////////////////
bool InputDeviceGamepadApple::GetPhysicalKeyOrButtonText(const InputChannelId& inputChannelId,
AZStd::string& o_keyOrButtonText) const
{
if (inputChannelId == InputDeviceGamepad::Button::Start)
{
o_keyOrButtonText = "Pause";
return true;
}
return false;
}
////////////////////////////////////////////////////////////////////////////////////////////////
GCController* GetControllerWithPreferredLayout(GCController* a, GCController* b)
{
if (a == nil)
{
return b;
}
else if (b == nil)
{
return a;
}
if (a.extendedGamepad != nil)
{
return a;
}
else if (b.extendedGamepad != nil)
{
return b;
}
// It doesn't matter
return a;
}
////////////////////////////////////////////////////////////////////////////////////////////////
GCController* FindUnassignedController(bool isPrimaryPlayer)
{
GCController* preferredUnassignedController = nil;
for (GCController* connectedController in GCController.controllers)
{
if (connectedController.playerIndex != GCControllerPlayerIndexUnset)
{
// This controller has already been assigned to a local player
continue;
}
if (connectedController.attachedToDevice)
{
// A controller attached to the device...
if (isPrimaryPlayer)
{
// ...should always be first preference for the primary player...
return connectedController;
}
else
{
// ...but should always be ignored for additional players.
continue;
}
}
preferredUnassignedController = GetControllerWithPreferredLayout(
preferredUnassignedController,
connectedController);
}
return preferredUnassignedController;
}
////////////////////////////////////////////////////////////////////////////////////////////////
bool IsControllerStillConnected(GCController* controller)
{
for (GCController* connectedController in GCController.controllers)
{
if (connectedController == controller)
{
return true;
}
}
return false;
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceGamepadApple::TickInputDevice()
{
if (m_controller == nil)
{
// Check if there are any connected controllers that have yet to be assigned to a player
const AZ::u32 deviceIndex = GetInputDeviceIndex();
m_controller = FindUnassignedController(deviceIndex == 0);
if (m_controller == nil)
{
// Could not find any connected controllers for this player
return;
}
// The controller connected since the last call to this function
#if !defined(__MAC_10_15) && !defined(__IPHONE_13_0) && !defined(__TVOS_13_0)
m_controller.controllerPausedHandler = ^(GCController* controller) { m_wasPausedHandlerCalled = true; };
#endif
m_controller.playerIndex = static_cast<GCControllerPlayerIndex>(deviceIndex);
BroadcastInputDeviceConnectedEvent();
}
else if (!IsControllerStillConnected(m_controller))
{
// The controller disconnected since the last call to this function
m_controller = nil;
m_rawGamepadState.Reset();
ResetInputChannelStates();
BroadcastInputDeviceDisconnectedEvent();
return;
}
AZ_Assert(m_controller != nil, "Logic error in InputDeviceGamepadApple::TickInputDevice");
// Always update the input channels while the game-pad is connected
m_rawGamepadState.m_digitalButtonStates = 0;
if (GCExtendedGamepad* extendedGamepad = m_controller.extendedGamepad)
{
if (extendedGamepad.dpad.up.pressed) { m_rawGamepadState.m_digitalButtonStates |= DIGITAL_BUTTON_MASK_DU; }
if (extendedGamepad.dpad.down.pressed) { m_rawGamepadState.m_digitalButtonStates |= DIGITAL_BUTTON_MASK_DD; }
if (extendedGamepad.dpad.left.pressed) { m_rawGamepadState.m_digitalButtonStates |= DIGITAL_BUTTON_MASK_DL; }
if (extendedGamepad.dpad.right.pressed) { m_rawGamepadState.m_digitalButtonStates |= DIGITAL_BUTTON_MASK_DR; }
if (extendedGamepad.leftShoulder.pressed) { m_rawGamepadState.m_digitalButtonStates |= DIGITAL_BUTTON_MASK_L1; }
if (extendedGamepad.rightShoulder.pressed) { m_rawGamepadState.m_digitalButtonStates |= DIGITAL_BUTTON_MASK_R1; }
#if defined(__MAC_10_14_1) || defined(__IPHONE_12_1) || defined(__TVOS_12_1)
if(@available(macOS 10.14.1, iOS 12.1, tvOS 12.1, *))
{
if (extendedGamepad.leftThumbstickButton.pressed) { m_rawGamepadState.m_digitalButtonStates |= DIGITAL_BUTTON_MASK_L3; }
if (extendedGamepad.rightThumbstickButton.pressed) { m_rawGamepadState.m_digitalButtonStates |= DIGITAL_BUTTON_MASK_R3; }
}
#endif
if (extendedGamepad.buttonA.pressed) { m_rawGamepadState.m_digitalButtonStates |= DIGITAL_BUTTON_MASK_A; }
if (extendedGamepad.buttonB.pressed) { m_rawGamepadState.m_digitalButtonStates |= DIGITAL_BUTTON_MASK_B; }
if (extendedGamepad.buttonX.pressed) { m_rawGamepadState.m_digitalButtonStates |= DIGITAL_BUTTON_MASK_X; }
if (extendedGamepad.buttonY.pressed) { m_rawGamepadState.m_digitalButtonStates |= DIGITAL_BUTTON_MASK_Y; }
#if defined(__MAC_10_15) || defined(__IPHONE_13_0) || defined(__TVOS_13_0)
if(@available(macOS 10.15, iOS 13.0, tvOS 13.0, *))
{
if (extendedGamepad.buttonMenu.pressed) { m_rawGamepadState.m_digitalButtonStates |= DIGITAL_BUTTON_MASK_PAUSE; }
if (extendedGamepad.buttonOptions.pressed) { m_rawGamepadState.m_digitalButtonStates |= DIGITAL_BUTTON_MASK_SELECT; }
}
#endif
m_rawGamepadState.m_triggerButtonLState = extendedGamepad.leftTrigger.value;
m_rawGamepadState.m_triggerButtonRState = extendedGamepad.rightTrigger.value;
m_rawGamepadState.m_thumbStickLeftXState = extendedGamepad.leftThumbstick.xAxis.value;
m_rawGamepadState.m_thumbStickLeftYState = extendedGamepad.leftThumbstick.yAxis.value;
m_rawGamepadState.m_thumbStickRightXState = extendedGamepad.rightThumbstick.xAxis.value;
m_rawGamepadState.m_thumbStickRightYState = extendedGamepad.rightThumbstick.yAxis.value;
}
else
{
AZ_WarningOnce("InputDeviceGamepadApple", false, "Unknown game-pad profile");
}
if (m_wasPausedHandlerCalled)
{
m_rawGamepadState.m_digitalButtonStates |= DIGITAL_BUTTON_MASK_PAUSE;
m_wasPausedHandlerCalled = false;
}
ProcessRawGamepadState(m_rawGamepadState);
}
} // namespace AzFramework
@@ -0,0 +1,301 @@
/*
* 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/Devices/VirtualKeyboard/InputDeviceVirtualKeyboard.h>
#include <AzFramework/Input/Events/InputChannelEventSink.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <UIKit/UIKit.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
@interface VirtualKeyboardTextFieldDelegate : NSObject <UITextFieldDelegate>
{
AzFramework::InputDeviceVirtualKeyboard::Implementation* m_inputDevice;
UITextField* m_textField;
@public
float m_activeTextFieldNormalizedBottomY;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
- (id)initWithInputDevice: (AzFramework::InputDeviceVirtualKeyboard::Implementation*)inputDevice
withTextField: (UITextField*)textField;
////////////////////////////////////////////////////////////////////////////////////////////////////
- (void)keyboardWillChangeFrame: (NSNotification*)notification;
////////////////////////////////////////////////////////////////////////////////////////////////////
- (BOOL)textFieldShouldClear: (UITextField*)textField;
////////////////////////////////////////////////////////////////////////////////////////////////////
- (BOOL)textFieldShouldReturn: (UITextField*)textField;
////////////////////////////////////////////////////////////////////////////////////////////////////
- (BOOL)textField: (UITextField*)textField
shouldChangeCharactersInRange: (NSRange)range
replacementString: (NSString*)string;
@end // VirtualKeyboardTextFieldDelegate interface
////////////////////////////////////////////////////////////////////////////////////////////////////
@implementation VirtualKeyboardTextFieldDelegate
////////////////////////////////////////////////////////////////////////////////////////////////////
- (id)initWithInputDevice: (AzFramework::InputDeviceVirtualKeyboard::Implementation*)inputDevice
withTextField: (UITextField*)textField
{
if ((self = [super init]))
{
self->m_inputDevice = inputDevice;
self->m_textField = textField;
// Resgister to be notified when the keyboard frame size changes so we can then adjust the
// position of the view accordingly to ensure we don't obscure the text field being edited.
// We don't need to explicitly remove the observer:
// https://developer.apple.com/library/mac/releasenotes/Foundation/RN-Foundation/index.html#10_11NotificationCenter
[[NSNotificationCenter defaultCenter] addObserver: self
selector: @selector(keyboardWillChangeFrame:)
name: UIKeyboardWillChangeFrameNotification
object: nil];
}
return self;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
- (void)keyboardWillChangeFrame: (NSNotification*)notification
{
if (!m_textField || !m_textField.superview)
{
return;
}
// Get the keyboard rect in terms of the view to account for orientation.
CGRect keyboardRect = [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
keyboardRect = [m_textField.superview convertRect: keyboardRect fromView: nil];
// Calculate the offset needed so the active text field is not being covered by the keyboard.
const double activeTextFieldBottom = m_activeTextFieldNormalizedBottomY * m_textField.superview.bounds.size.height;
const double offsetY = AZ::GetMin(0.0, keyboardRect.origin.y - activeTextFieldBottom);
// Create the offset view rect and transform it into the coordinate space of the main window.
CGRect offsetViewRect = CGRectMake(0, offsetY, m_textField.superview.bounds.size.width,
m_textField.superview.bounds.size.height);
offsetViewRect = [m_textField.superview convertRect: offsetViewRect toView: nil];
// Remove any existing offset applied in previous calls to this function.
offsetViewRect.origin.x -= m_textField.superview.frame.origin.x;
offsetViewRect.origin.y -= m_textField.superview.frame.origin.y;
m_textField.superview.frame = offsetViewRect;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
- (BOOL)textFieldShouldClear: (UITextField*)textField
{
// Queue an 'clear' command event.
m_inputDevice->QueueRawCommandEvent(AzFramework::InputDeviceVirtualKeyboard::Command::EditClear);
// Return false so that the text field itself does not update.
return FALSE;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
- (BOOL)textFieldShouldReturn: (UITextField*)textField
{
// Queue an 'enter' command event.
m_inputDevice->QueueRawCommandEvent(AzFramework::InputDeviceVirtualKeyboard::Command::EditEnter);
// Return false so that the text field itself does not update.
return FALSE;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
- (BOOL)textField: (UITextField*)textField
shouldChangeCharactersInRange: (NSRange)range
replacementString: (NSString*)string
{
// If the string length is 0, the user has pressed the backspace key on the virtual keyboard.
const AZStd::string textUTF8 = string.length ? string.UTF8String : "\b";
m_inputDevice->QueueRawTextEvent(textUTF8);
// Return false so that the text field itself does not update.
return FALSE;
}
@end // VirtualKeyboardTextFieldDelegate implementation
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace AzFramework
{
////////////////////////////////////////////////////////////////////////////////////////////////
//! Platform specific implementation for ios virtual keyboard input devices
class InputDeviceVirtualKeyboardApple : public InputDeviceVirtualKeyboard::Implementation
{
public:
////////////////////////////////////////////////////////////////////////////////////////////
// Allocator
AZ_CLASS_ALLOCATOR(InputDeviceVirtualKeyboardApple, AZ::SystemAllocator, 0);
////////////////////////////////////////////////////////////////////////////////////////////
//! Constructor
//! \param[in] inputDevice Reference to the input device being implemented
InputDeviceVirtualKeyboardApple(InputDeviceVirtualKeyboard& inputDevice);
////////////////////////////////////////////////////////////////////////////////////////////
//! Destructor
~InputDeviceVirtualKeyboardApple() override;
private:
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceVirtualKeyboard::Implementation::IsConnected
bool IsConnected() const override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceVirtualKeyboard::Implementation::HasTextEntryStarted
bool HasTextEntryStarted() const override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceVirtualKeyboard::Implementation::TextEntryStart
void TextEntryStart(const InputTextEntryRequests::VirtualKeyboardOptions& options) override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceVirtualKeyboard::Implementation::TextEntryStop
void TextEntryStop() override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceVirtualKeyboard::Implementation::TickInputDevice
void TickInputDevice() override;
////////////////////////////////////////////////////////////////////////////////////////////
// Variables
UITextField* m_textField = nullptr;
VirtualKeyboardTextFieldDelegate* m_textFieldDelegate = nullptr;
AZStd::unique_ptr<InputChannelEventSink> m_inputChannelEventSink;
};
////////////////////////////////////////////////////////////////////////////////////////////////
InputDeviceVirtualKeyboard::Implementation* InputDeviceVirtualKeyboard::Implementation::Create(
InputDeviceVirtualKeyboard& inputDevice)
{
return aznew InputDeviceVirtualKeyboardApple(inputDevice);
}
////////////////////////////////////////////////////////////////////////////////////////////////
InputDeviceVirtualKeyboardApple::InputDeviceVirtualKeyboardApple(
InputDeviceVirtualKeyboard& inputDevice)
: InputDeviceVirtualKeyboard::Implementation(inputDevice)
, m_textField(nullptr)
, m_textFieldDelegate(nullptr)
{
// Create a UITextField that we can call becomeFirstResponder on to show the keyboard.
m_textField = [[UITextField alloc] initWithFrame: CGRectZero];
// Create and set the text field's delegate so we can respond to keyboard input.
m_textFieldDelegate = [[VirtualKeyboardTextFieldDelegate alloc] initWithInputDevice: this withTextField: m_textField];
m_textField.delegate = m_textFieldDelegate;
// Disable autocapitalization and autocorrection, which both behave strangely.
m_textField.autocapitalizationType = UITextAutocapitalizationTypeNone;
m_textField.autocorrectionType = UITextAutocorrectionTypeNo;
// Hide the text field so it will never actually be shown.
m_textField.hidden = YES;
// Add something to the text field so delete works.
m_textField.text = @" ";
}
////////////////////////////////////////////////////////////////////////////////////////////////
InputDeviceVirtualKeyboardApple::~InputDeviceVirtualKeyboardApple()
{
if (m_textField)
{
m_textField.delegate = nullptr;
[m_textFieldDelegate release];
m_textFieldDelegate = nullptr;
[m_textField removeFromSuperview];
[m_textField release];
m_textField = nullptr;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
bool InputDeviceVirtualKeyboardApple::IsConnected() const
{
// Virtual keyboard input is always available
return true;
}
////////////////////////////////////////////////////////////////////////////////////////////////
bool InputDeviceVirtualKeyboardApple::HasTextEntryStarted() const
{
return m_textField ? m_textField.isFirstResponder : false;
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceVirtualKeyboardApple::TextEntryStart(const InputTextEntryRequests::VirtualKeyboardOptions& options)
{
UIWindow* foundWindow = nil;
#if defined(__IPHONE_13_0) || defined(__TVOS_13_0)
if(@available(iOS 13.0, tvOS 13.0, *))
{
NSArray* windows = [[UIApplication sharedApplication] windows];
for (UIWindow* window in windows)
{
if (window.isKeyWindow)
{
foundWindow = window;
break;
}
}
}
#else
foundWindow = [[UIApplication sharedApplication] keyWindow];
#endif
// Get the application's root view.
UIView* rootView = foundWindow ? foundWindow.rootViewController.view : nullptr;
if (!rootView)
{
return;
}
// Add the text field to the root view.
[rootView addSubview: m_textField];
// On iOS we must set m_activeTextFieldNormalizedBottomY before showing the virtual keyboard
// by calling becomeFirstResponder, which then sends a UIKeyboardWillChangeFrameNotification.
m_textFieldDelegate->m_activeTextFieldNormalizedBottomY = options.m_normalizedMinY;
[m_textField becomeFirstResponder];
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceVirtualKeyboardApple::TextEntryStop()
{
// On iOS we must set m_activeTextFieldNormalizedBottomY before hiding the virtual keyboard
// by calling resignFirstResponder, which then sends a UIKeyboardWillChangeFrameNotification.
m_textFieldDelegate->m_activeTextFieldNormalizedBottomY = 0.0f;
[m_textField resignFirstResponder];
[m_textField removeFromSuperview];
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceVirtualKeyboardApple::TickInputDevice()
{
// The ios event loop has just been pumped in InputSystemComponentIos::PreTickInputDevices,
// so we now just need to process any raw events that have been queued since the last frame
ProcessRawEventQueues();
}
} // namespace AzFramework
@@ -0,0 +1,32 @@
/*
* 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/base.h>
#include <AzCore/RTTI/BehaviorContext.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace AzFramework
{
////////////////////////////////////////////////////////////////////////////////////////////////
//! Alias for the type of a local user id
using LocalUserId = AZ::u32;
////////////////////////////////////////////////////////////////////////////////////////////////
//! Reflection (AZ::u32 is already reflected)
inline void LocalUserIdReflect(AZ::ReflectContext* context) { AZ_UNUSED(context); }
////////////////////////////////////////////////////////////////////////////////////////////////
//! Convert to a string (use for debugging purposes only)
inline AZStd::string LocalUserIdToString(const LocalUserId& localUserId) { return AZStd::string::format("%u", localUserId); }
} // namespace AzFramework
@@ -0,0 +1,28 @@
/*
* 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 <stdio.h>
namespace AzFramework
{
namespace AssetSystem
{
namespace Platform
{
void DebugOutput(const char* message)
{
fputs("AssetProcessorConnection:", stdout);
fputs(message, stdout);
}
}
}
}
@@ -0,0 +1,29 @@
/*
* 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/std/string/string.h>
namespace AzFramework
{
namespace Platform
{
AZStd::string GetPersistentName()
{
return { "Lumberyard" };
}
AZStd::string GetNeighborhoodName()
{
return {};
}
}
}
@@ -0,0 +1,23 @@
/*
* 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/std/string/string_view.h>
namespace AzFramework::AssetSystem::Platform
{
void AllowAssetProcessorToForeground()
{}
bool LaunchAssetProcessor(AZStd::string_view, AZStd::string_view, AZStd::string_view)
{
return false;
}
}
@@ -0,0 +1,29 @@
/*
* 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/Devices/Gamepad/InputDeviceGamepad.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace AzFramework
{
////////////////////////////////////////////////////////////////////////////////////////////////
AZ::u32 InputDeviceGamepad::GetMaxSupportedGamepads()
{
return 0;
}
////////////////////////////////////////////////////////////////////////////////////////////////
InputDeviceGamepad::Implementation* InputDeviceGamepad::Implementation::Create(InputDeviceGamepad&)
{
return nullptr;
}
} // namespace AzFramework
@@ -0,0 +1,23 @@
/*
* 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/Devices/Keyboard/InputDeviceKeyboard.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace AzFramework
{
////////////////////////////////////////////////////////////////////////////////////////////////
InputDeviceKeyboard::Implementation* InputDeviceKeyboard::Implementation::Create(InputDeviceKeyboard&)
{
return nullptr;
}
} // namespace AzFramework
@@ -0,0 +1,23 @@
/*
* 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/Devices/Motion/InputDeviceMotion.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace AzFramework
{
////////////////////////////////////////////////////////////////////////////////////////////////
InputDeviceMotion::Implementation* InputDeviceMotion::Implementation::Create(InputDeviceMotion&)
{
return nullptr;
}
} // namespace AzFramework
@@ -0,0 +1,23 @@
/*
* 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/Devices/Mouse/InputDeviceMouse.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace AzFramework
{
////////////////////////////////////////////////////////////////////////////////////////////////
InputDeviceMouse::Implementation* InputDeviceMouse::Implementation::Create(InputDeviceMouse&)
{
return nullptr;
}
} // namespace AzFramework
@@ -0,0 +1,23 @@
/*
* 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/Devices/Touch/InputDeviceTouch.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace AzFramework
{
////////////////////////////////////////////////////////////////////////////////////////////////
InputDeviceTouch::Implementation* InputDeviceTouch::Implementation::Create(InputDeviceTouch&)
{
return nullptr;
}
} // namespace AzFramework
@@ -0,0 +1,24 @@
/*
* 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/Devices/VirtualKeyboard/InputDeviceVirtualKeyboard.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace AzFramework
{
////////////////////////////////////////////////////////////////////////////////////////////////
InputDeviceVirtualKeyboard::Implementation* InputDeviceVirtualKeyboard::Implementation::Create(
InputDeviceVirtualKeyboard&)
{
return nullptr;
}
} // namespace AzFramework
@@ -0,0 +1,25 @@
/*
* 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/StreamingInstall/StreamingInstall.h>
namespace AzFramework
{
namespace StreamingInstall
{
StreamingInstallSystemComponent::Implementation* StreamingInstallSystemComponent::Implementation::Create(StreamingInstallSystemComponent&)
{
return nullptr;
}
}
}
@@ -0,0 +1,169 @@
/*
* 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 <fstream>
#include <sys/stat.h>
#include <dirent.h>
#include <unistd.h>
#include <AzFramework/IO/LocalFileIO.h>
#include <AzCore/IO/SystemFile.h>
#include <AzCore/std/functional.h>
namespace AZ
{
namespace IO
{
bool LocalFileIO::IsDirectory(const char* filePath)
{
char resolvedPath[AZ_MAX_PATH_LEN] = {0};
ResolvePath(filePath, resolvedPath, AZ_MAX_PATH_LEN);
struct stat result;
if (stat(resolvedPath, &result) == 0)
{
return S_ISDIR(result.st_mode);
}
return false;
}
Result LocalFileIO::Copy(const char* sourceFilePath, const char* destinationFilePath)
{
char resolvedSourceFilePath[AZ_MAX_PATH_LEN] = {0};
ResolvePath(sourceFilePath, resolvedSourceFilePath, AZ_MAX_PATH_LEN);
char resolvedDestinationFilePath[AZ_MAX_PATH_LEN] = {0};
ResolvePath(destinationFilePath, resolvedDestinationFilePath, AZ_MAX_PATH_LEN);
// Use standard C++ method of file copy.
{
std::ifstream src(resolvedSourceFilePath, std::ios::binary);
if (src.fail())
{
return ResultCode::Error;
}
std::ofstream dst(resolvedDestinationFilePath, std::ios::binary);
if (dst.fail())
{
return ResultCode::Error;
}
dst << src.rdbuf();
}
return ResultCode::Success;
}
Result LocalFileIO::FindFiles(const char* filePath, const char* filter, FindFilesCallbackType callback)
{
char resolvedPath[AZ_MAX_PATH_LEN] = {0};
ResolvePath(filePath, resolvedPath, AZ_MAX_PATH_LEN);
AZ::OSString withoutSlash = RemoveTrailingSlash(resolvedPath);
DIR* dir = opendir(withoutSlash.c_str());
if (dir != nullptr)
{
// because the absolute path might actually be SHORTER than the alias ("c:/r/dev" -> "@devroot@"), we need to
// use a static buffer here.
char tempBuffer[AZ_MAX_PATH_LEN];
errno = 0;
struct dirent* entry = readdir(dir);
// List all the other files in the directory.
while (entry != nullptr)
{
AZStd::string_view filenameView = entry->d_name;
// Skip over the current and parent directory paths
if (filenameView != "." && filenameView != ".." && NameMatchesFilter(entry->d_name, filter))
{
AZ::OSString foundFilePath = CheckForTrailingSlash(resolvedPath);
foundFilePath += entry->d_name;
// if aliased, dealias!
azstrcpy(tempBuffer, AZ_MAX_PATH_LEN, foundFilePath.c_str());
ConvertToAlias(tempBuffer, AZ_MAX_PATH_LEN);
if (!callback(tempBuffer))
{
break;
}
}
entry = readdir(dir);
}
closedir(dir);
return (errno != 0) ? ResultCode::Error : ResultCode::Success;
}
else
{
return ResultCode::Error;
}
}
Result LocalFileIO::CreatePath(const char* filePath)
{
char resolvedPath[AZ_MAX_PATH_LEN] = {0};
ResolvePath(filePath, resolvedPath, AZ_MAX_PATH_LEN);
// create all paths up to that directory.
// its not an error if the path exists.
if ((Exists(resolvedPath)) && (!IsDirectory(resolvedPath)))
{
return ResultCode::Error; // that path exists, but is not a directory.
}
// make directories from bottom to top.
AZ::OSString buf;
size_t pathLength = strlen(resolvedPath);
buf.reserve(pathLength);
for (size_t pos = 0; pos < pathLength; ++pos)
{
if ((resolvedPath[pos] == '\\') || (resolvedPath[pos] == '/'))
{
if (pos > 0)
{
mkdir(buf.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
if (!IsDirectory(buf.c_str()))
{
return ResultCode::Error;
}
}
}
buf.push_back(resolvedPath[pos]);
}
mkdir(buf.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
return IsDirectory(resolvedPath) ? ResultCode::Success : ResultCode::Error;
}
bool LocalFileIO::IsAbsolutePath(const char* path) const
{
return path && path[0] == '/';
}
bool LocalFileIO::ConvertToAbsolutePath(const char* path, char* absolutePath, AZ::u64 maxLength) const
{
AZ_Assert(maxLength >= AZ_MAX_PATH_LEN, "Path length is larger than AZ_MAX_PATH_LEN");
if (!IsAbsolutePath(path))
{
// note that realpath fails if the path does not exist and actually changes the return value
// to be the actual place that FAILED, which we don't want.
// if we fail, we'd prefer to fall through and at least use the original path.
const char* result = realpath(path, absolutePath);
if (result)
{
return true;
}
}
azstrcpy(absolutePath, maxLength, path);
return IsAbsolutePath(absolutePath);
}
} // namespace IO
} // namespace AZ
@@ -0,0 +1,181 @@
/*
* 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>
#include <AzCore/std/functional.h>
namespace AZ
{
namespace IO
{
bool LocalFileIO::IsDirectory(const char* filePath)
{
char resolvedPath[AZ_MAX_PATH_LEN];
ResolvePath(filePath, resolvedPath, AZ_MAX_PATH_LEN);
DWORD fileAttributes = GetFileAttributesA(resolvedPath);
if (fileAttributes == INVALID_FILE_ATTRIBUTES)
{
return false;
}
return (fileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
}
Result LocalFileIO::FindFiles(const char* filePath, const char* filter, FindFilesCallbackType callback)
{
char resolvedPath[AZ_MAX_PATH_LEN];
ResolvePath(filePath, resolvedPath, AZ_MAX_PATH_LEN);
AZ::OSString searchPattern;
if ((resolvedPath[0] == 0) || (resolvedPath[1] == 0))
{
return ResultCode::Error; // not a valid path.
}
if ((strchr(resolvedPath, ':')) || (resolvedPath[0] == '\\') || (resolvedPath[0] == '/'))
{
// an absolute path was provided
searchPattern = resolvedPath;
AZStd::replace(searchPattern.begin(), searchPattern.end(), '/', '\\');
searchPattern = RemoveTrailingSlash(searchPattern);
}
else
{
searchPattern = RemoveTrailingSlash(resolvedPath);
}
searchPattern += "\\*.*"; // use our own filtering function!
WIN32_FIND_DATA findData;
HANDLE hFind = FindFirstFile(searchPattern.c_str(), &findData);
if (hFind != INVALID_HANDLE_VALUE)
{
// because the absolute path might actually be SHORTER than the alias ("c:/r/dev" -> "@devroot@"), we need to
// use a static buffer here.
char tempBuffer[AZ_MAX_PATH_LEN];
do
{
AZStd::string_view filenameView = findData.cFileName;
// Skip over the current directory and parent directory paths to prevent infinite recursion
if (filenameView == "." || filenameView == ".." || !NameMatchesFilter(findData.cFileName, filter))
{
continue;
}
AZ::OSString foundFilePath = CheckForTrailingSlash(resolvedPath);
foundFilePath += findData.cFileName;
AZStd::replace(foundFilePath.begin(), foundFilePath.end(), '\\', '/');
// if aliased, de-alias!
azstrcpy(tempBuffer, AZ_MAX_PATH_LEN, foundFilePath.c_str());
ConvertToAlias(tempBuffer, AZ_MAX_PATH_LEN);
if (!callback(tempBuffer))
{
//we are done
FindClose(hFind);
return ResultCode::Success;
}
} while (FindNextFile(hFind, &findData));
FindClose(hFind);
return ResultCode::Success;
}
return ResultCode::Error;
}
int64_t ConvertUnixStatFileTimeToWindowsFileTime(int64_t timeValue)
{
// KB article on microsoft : https://support.microsoft.com/en-us/kb/167296
// you need to adjust for the base time epoch difference between unix epoch and FILETIME epoch!
// note that Int32x32To64 is a windows only function.
// the magic numbers represent the difference in epoch between the windows and unix file time units
// (in FILETIME units, which are 100-nanosecond intervals, starting on Jan 1, 1601 UTC.)
// the first 10,000,000 is the number of 100-nanosecond intervals in 1 second
// the second 116,444,736,000,000,000 is the number of nanoseconds between unix epoch start
// which started on Jan 1, 1970 UTC (369 years worth of 100-nanosecond chunks), and the windows
// filetime epoch.
int64_t longTime = Int32x32To64(timeValue, 10000000) + 116444736000000000;
return longTime;
}
Result LocalFileIO::CreatePath(const char* filePath)
{
char resolvedPath[AZ_MAX_PATH_LEN];
ResolvePath(filePath, resolvedPath, AZ_MAX_PATH_LEN);
// create all paths up to that directory.
// its not an error if the path exists.
if ((Exists(resolvedPath)) && (!IsDirectory(resolvedPath)))
{
return ResultCode::Error; // that path exists, but is not a directory.
}
// make directories from bottom to top.
AZ::OSString buf;
size_t pathLength = strlen(resolvedPath);
buf.reserve(pathLength);
for (size_t pos = 0; pos < pathLength; ++pos)
{
if ((resolvedPath[pos] == '\\') || (resolvedPath[pos] == '/'))
{
if (pos > 0)
{
CreateDirectoryA(buf.c_str(), NULL);
if (!IsDirectory(buf.c_str()))
{
return ResultCode::Error;
}
}
}
buf.push_back(resolvedPath[pos]);
}
return SystemFile::CreateDir(buf.c_str()) ? ResultCode::Success : ResultCode::Error;
}
bool LocalFileIO::ConvertToAbsolutePath(const char* path, char* absolutePath, AZ::u64 maxLength) const
{
char* result = _fullpath(absolutePath, path, maxLength);
size_t len = ::strlen(absolutePath);
if (len > 0)
{
// strip trailing slash
if (absolutePath[len - 1] == '/' || absolutePath[len - 1] == '\\')
{
absolutePath[len - 1] = 0;
}
// For some reason, at least on windows, _fullpath returns a lowercase drive letter even though other systems like Qt, use upper case.
if (len > 2)
{
if (absolutePath[1] == ':')
{
absolutePath[0] = (char)toupper(absolutePath[0]);
}
}
}
return result != nullptr;
}
bool LocalFileIO::IsAbsolutePath(const char* path) const
{
char drive[16];
_splitpath_s(path, drive, 16, nullptr, 0, nullptr, 0, nullptr, 0);
return strlen(drive) > 0;
}
} // namespace IO
}//namespace AZ
@@ -0,0 +1,152 @@
/*
* 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/Keyboard/InputDeviceKeyboardWindowsScanCodes.h>
#include <AzCore/std/string/conversions.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace AzFramework
{
////////////////////////////////////////////////////////////////////////////////////////////////
//! Class used to convert sequences of UTF-16 code units to UTF-8 code points
class UTF16ToUTF8Converter
{
public:
////////////////////////////////////////////////////////////////////////////////////////////
//! Feed a UTF-16 code unit to the converter
//!
//! \param[in] codeUnitUTF16 The UTF-16 code unit to be converted. The code unit could be a
//! standalone code point, in which case it is converted immediately. Or it could form part
//! of a surrogate pair, in which case we rely on the lead and trailing surrogate being fed
//! to this function in succession before the converted UTF-8 code point can be returned.
//!
//! \return If codeUnitUTF16 is a lead surrogate it is stored internally and an empty string
//! returned. If codeUnitUTF16 is a trailing surrogate and forms a valid surrogate pair with
//! the currently stored lead surrogate, the resulting UTF-16 code point is converted to the
//! corresponding UTF-8 code point, and returned as a UTF-8 encoded string. If codeUnitUTF16
//! is neither a lead or trailing surrogate, it is converted to the corresponding UTF-8 code
//! point, and returned immediately as a UTF-8 encoded string. Any other case is an encoding
//! error that will result in an empty string being returned.
AZStd::string FeedCodeUnitUTF16(uint16_t codeUnitUTF16);
private:
////////////////////////////////////////////////////////////////////////////////////////////
//! If a codeUnitUTF16 passed to FeedCodeUnitUTF16 is part of a surrogate pair we must store
//! the 'lead surrogate' so the subsequent 'trailing surrogate' can be correctly interpreted.
uint16_t m_leadSurrogate = 0;
};
////////////////////////////////////////////////////////////////////////////////////////////////
inline AZStd::string UTF16ToUTF8Converter::FeedCodeUnitUTF16(uint16_t codeUnitUTF16)
{
AZStd::string codePointUTF8;
if (IS_HIGH_SURROGATE(codeUnitUTF16))
{
// Store the lead surrogate and wait for the trailing surrogate
m_leadSurrogate = codeUnitUTF16;
}
else if (IS_LOW_SURROGATE(codeUnitUTF16))
{
if (m_leadSurrogate)
{
// Convert the valid UTF-16 surrogate pair to a UTF-8 code point
const wchar_t codePointUTF16[2] = { m_leadSurrogate, codeUnitUTF16 };
AZStd::to_string(codePointUTF8, codePointUTF16, 2);
m_leadSurrogate = 0;
}
else
{
// Encoding error
m_leadSurrogate = 0;
}
}
else
{
// Convert the standalone UTF-16 code point to a UTF-8 code point
const wchar_t codePointUTF16[1] = { codeUnitUTF16 };
AZStd::to_string(codePointUTF8, codePointUTF16, 1);
m_leadSurrogate = 0;
}
return codePointUTF8;
}
////////////////////////////////////////////////////////////////////////////////////////////////
//! Get the input channel id that corresponds to a raw keyboard key event
//! \param[in] scanCode The Windows scan code of the key that was pressed
//! \param[in] virtualKeyCode The Windows virtual key code that was generated by the key press
//! \param[in] hasExtendedKeyPrefix Does the Windows scan code have the extended key prefix set?
//! \return The corresponding input channel id if found, nullptr otherwise
inline const InputChannelId* GetInputChannelIdFromRawKeyEvent(AZ::u32 scanCode,
AZ::u32 virtualKeyCode,
bool hasExtendedKeyPrefix)
{
if (scanCode >= InputChannelIdByScanCodeTable.size() ||
virtualKeyCode >= InputChannelIdByVirtualKeyCodeTable.size())
{
// Discard escaped sequences
return nullptr;
}
// First look for the channel id using the scan code
const InputChannelId* channelId = !hasExtendedKeyPrefix ?
InputChannelIdByScanCodeTable[scanCode] :
InputChannelIdByScanCodeWithExtendedPrefixTable[scanCode];
// If we couldn't find the channelId from the scan code, try using the virtual key code.
// Also, because the pause key generates the same scan code as the numlock key, we must
// check the virtual key code in this case to distinguish between the two physical keys.
if (!channelId || virtualKeyCode == VK_PAUSE)
{
channelId = InputChannelIdByVirtualKeyCodeTable[virtualKeyCode];
}
return channelId;
}
////////////////////////////////////////////////////////////////////////////////////////////////
//! Construct a map of Windows scan codes indexed by their corresponding input channel id
//! \return A map of Windows scan codes indexed by their corresponding input channel id
inline AZStd::unordered_map<InputChannelId, AZ::u32> ConstructScanCodeByInputChannelIdMap()
{
AZStd::unordered_map<InputChannelId, AZ::u32> scanCodesByInputChannelId;
for (AZ::u32 scanCode = 0; scanCode < InputChannelIdByScanCodeTable.size(); ++scanCode)
{
const InputChannelId* inputChannelId = InputChannelIdByScanCodeTable[scanCode];
if (inputChannelId != nullptr)
{
scanCodesByInputChannelId[*inputChannelId] = scanCode;
}
}
return scanCodesByInputChannelId;
}
////////////////////////////////////////////////////////////////////////////////////////////////
//! Get the Windows scan code that corresponds to an input channel id
//! \param[in] inputChannelId The input channel id whose corresponding scan code to return
//! \return The corresponding Windows scan code id if found, 0 otherwise
inline AZ::u32 GetScanCodeFromInputChannelId(const InputChannelId& inputChannelId)
{
for (AZ::u32 scanCode = 0; scanCode < InputChannelIdByScanCodeTable.size(); ++scanCode)
{
const InputChannelId* inputChannelIdOfScanCode = InputChannelIdByScanCodeTable[scanCode];
if (inputChannelIdOfScanCode != nullptr && *inputChannelIdOfScanCode == inputChannelId)
{
return scanCode;
}
}
return 0;
}
} // namespace AzFramework
@@ -0,0 +1,29 @@
/*
* 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/base.h>
namespace AzFramework
{
namespace AssetSystem
{
namespace Platform
{
void DebugOutput(const char* message)
{
OutputDebugString("AssetProcessorConnection:");
OutputDebugString(message);
}
}
}
}
@@ -0,0 +1,32 @@
/*
* 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 LinuxLifecycleEvents
: public AZ::EBusTraits
{
public:
// Bus Configuration
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
virtual ~LinuxLifecycleEvents() {}
using Bus = AZ::EBus<LinuxLifecycleEvents>;
};
} // 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/API/ApplicationAPI_Linux.h>
@@ -0,0 +1,63 @@
/*
* 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/API/ApplicationAPI_Platform.h>
#include <AzFramework/Application/Application.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace AzFramework
{
////////////////////////////////////////////////////////////////////////////////////////////////
class ApplicationLinux
: public Application::Implementation
, public LinuxLifecycleEvents::Bus::Handler
{
public:
////////////////////////////////////////////////////////////////////////////////////////////
AZ_CLASS_ALLOCATOR(ApplicationLinux, AZ::SystemAllocator, 0);
ApplicationLinux();
~ApplicationLinux() override;
////////////////////////////////////////////////////////////////////////////////////////////
// Application::Implementation
void PumpSystemEventLoopOnce() override;
void PumpSystemEventLoopUntilEmpty() override;
};
////////////////////////////////////////////////////////////////////////////////////////////////
Application::Implementation* Application::Implementation::Create()
{
return aznew ApplicationLinux();
}
////////////////////////////////////////////////////////////////////////////////////////////////
ApplicationLinux::ApplicationLinux()
{
LinuxLifecycleEvents::Bus::Handler::BusConnect();
}
////////////////////////////////////////////////////////////////////////////////////////////////
ApplicationLinux::~ApplicationLinux()
{
LinuxLifecycleEvents::Bus::Handler::BusDisconnect();
}
////////////////////////////////////////////////////////////////////////////////////////////////
void ApplicationLinux::PumpSystemEventLoopOnce()
{
}
////////////////////////////////////////////////////////////////////////////////////////////////
void ApplicationLinux::PumpSystemEventLoopUntilEmpty()
{
}
} // namespace AzFramework
@@ -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,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_Linux.h>
@@ -0,0 +1,100 @@
/*
* 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/IO/Path/Path.h>
#include <AzCore/IO/SystemFile.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include <AzCore/std/containers/array.h>
#include <AzCore/std/tuple.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
namespace AzFramework::AssetSystem::Platform
{
void AllowAssetProcessorToForeground()
{}
bool LaunchAssetProcessor(AZStd::string_view executableDirectory, AZStd::string_view appRoot,
AZStd::string_view gameProjectName)
{
pid_t firstChildPid = fork();
if (firstChildPid == 0)
{
// redirect output to dev/null so it doesn't hijack an existing console window
const char* devNull = AZ::IO::SystemFile::GetNullFilename();
AZ::IO::FileDescriptorRedirector::Mode mode = AZ::IO::FileDescriptorRedirector::Mode::Create;
AZ::IO::FileDescriptorRedirector stdoutRedirect(STDOUT_FILENO);
stdoutRedirect.RedirectTo(devNull, mode);
AZ::IO::FileDescriptorRedirector stderrRedirect(STDERR_FILENO);
stderrRedirect.RedirectTo(devNull, mode);
// detach the child from parent
setsid();
pid_t secondChildPid = fork();
if (secondChildPid == 0)
{
AZ::IO::FixedMaxPath assetProcessorPath{ executableDirectory };
assetProcessorPath /= "AssetProcessor";
AZStd::array args {
assetProcessorPath.c_str(), assetProcessorPath.c_str(), "--start-hidden",
static_cast<const char*>(nullptr), static_cast<const char*>(nullptr), static_cast<const char*>(nullptr)
};
int optionalArgPos = 3;
// Add the app-root to the launch command if not empty
AZ::IO::FixedMaxPathString appRootArg;
if (!appRoot.empty())
{
appRootArg = AZ::IO::FixedMaxPathString::format(R"(--app-root="%.*s")",
aznumeric_cast<int>(appRoot.size()), appRoot.data());
args[optionalArgPos++] = appRootArg.data();
}
// Add the active game project to the launch command if not empty
AZ::IO::FixedMaxPathString projectArg;
if (!gameProjectName.empty())
{
projectArg = AZ::IO::FixedMaxPathString::format(R"(--regset="/Amazon/AzCore/Bootstrap/sys_game_folder=%.*s")",
aznumeric_cast<int>(gameProjectName.size()), gameProjectName.data());
args[optionalArgPos++] = projectArg.data();
}
AZStd::apply(execl, args);
// exec* family of functions only exit on error
AZ_Error("AssetSystemComponent", false, "Asset Processor failed with error: %s", strerror(errno));
_exit(1);
}
stdoutRedirect.Reset();
stderrRedirect.Reset();
// exit the transient child with proper return code
int ret = (secondChildPid < 0) ? 1 : 0;
_exit(ret);
}
else if (firstChildPid > 0)
{
// wait for first child to exit to ensure the second child was started
int status = 0;
pid_t ret = waitpid(firstChildPid, &status, 0);
return (ret == firstChildPid) && (status == 0);
}
return false;
}
}
@@ -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 (true)
#define AZ_TRAIT_AZFRAMEWORK_BOOTSTRAP_CFG_CURRENT_PLATFORM "linux"
#define AZ_TRAIT_AZFRAMEWORK_USE_C11_LOCALTIME_S 0
#define AZ_TRAIT_AZFRAMEWORK_USE_ERRNO_T_TYPEDEF 1
#define AZ_TRAIT_AZFRAMEWORK_USE_POSIX_LOCALTIME_R 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_Linux.h>
@@ -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,51 @@
/*
* 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/Windowing/NativeWindow.h>
namespace AzFramework
{
class NativeWindowImpl_Linux final
: public NativeWindow::Implementation
{
public:
AZ_CLASS_ALLOCATOR(NativeWindowImpl_Linux, AZ::SystemAllocator, 0);
NativeWindowImpl_Linux() = default;
~NativeWindowImpl_Linux() override = default;
// NativeWindow::Implementation overrides...
void InitWindow(const AZStd::string& title,
const WindowGeometry& geometry,
const WindowStyleMasks& styleMasks) override;
NativeWindowHandle GetWindowHandle() const override;
};
NativeWindow::Implementation* NativeWindow::Implementation::Create()
{
return aznew NativeWindowImpl_Linux();
}
void NativeWindowImpl_Linux::InitWindow([[maybe_unused]]const AZStd::string& title,
const WindowGeometry& geometry,
[[maybe_unused]]const WindowStyleMasks& styleMasks)
{
m_width = geometry.m_width;
m_height = geometry.m_height;
}
NativeWindowHandle NativeWindowImpl_Linux::GetWindowHandle() const
{
AZ_Assert(false, "NativeWindow not implemented for Linux");
return nullptr;
}
} // namespace AzFramework
@@ -0,0 +1,10 @@
#
# 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,34 @@
#
# 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_Linux.h
AzFramework/API/ApplicationAPI_Platform.h
AzFramework/API/ApplicationAPI_Linux.h
AzFramework/Application/Application_Linux.cpp
AzFramework/Asset/AssetSystemComponentHelper_Linux.cpp
../Common/UnixLike/AzFramework/IO/LocalFileIO_UnixLike.cpp
../Common/Default/AzFramework/Network/AssetProcessorConnection_Default.cpp
../Common/Unimplemented/AzFramework/StreamingInstall/StreamingInstall_Unimplemented.cpp
../Common/Default/AzFramework/TargetManagement/TargetManagementComponent_Default.cpp
AzFramework/Windowing/NativeWindow_Linux.cpp
../Common/Unimplemented/AzFramework/Input/Devices/Gamepad/InputDeviceGamepad_Unimplemented.cpp
../Common/Unimplemented/AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard_Unimplemented.cpp
../Common/Unimplemented/AzFramework/Input/Devices/Motion/InputDeviceMotion_Unimplemented.cpp
../Common/Unimplemented/AzFramework/Input/Devices/Mouse/InputDeviceMouse_Unimplemented.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_Linux.h
)
@@ -0,0 +1,46 @@
/*
* 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 DarwinLifecycleEvents
: public AZ::EBusTraits
{
public:
// Bus Configuration
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
virtual ~DarwinLifecycleEvents() {}
using Bus = AZ::EBus<DarwinLifecycleEvents>;
virtual void OnWillResignActive() {}
virtual void OnDidResignActive() {} // Constrain
virtual void OnWillBecomeActive() {}
virtual void OnDidBecomeActive() {} // Unconstrain
virtual void OnWillHide() {}
virtual void OnDidHide() {} // Suspend
virtual void OnWillUnhide() {}
virtual void OnDidUnhide() {} // Resume
virtual void OnWillTerminate() {} // Terminate
};
} // 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/API/ApplicationAPI_Mac.h>
@@ -0,0 +1,390 @@
/*
* 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/API/ApplicationAPI_Platform.h>
#include <AzFramework/Application/Application.h>
#include <AzFramework/Input/Buses/Notifications/RawInputNotificationBus_Platform.h>
#include <AppKit/NSApplication.h>
#include <AppKit/NSEvent.h>
#include <objc/runtime.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace AzFramework
{
////////////////////////////////////////////////////////////////////////////////////////////////
class ApplicationMac
: public Application::Implementation
, public DarwinLifecycleEvents::Bus::Handler
{
public:
////////////////////////////////////////////////////////////////////////////////////////////
AZ_CLASS_ALLOCATOR(ApplicationMac, AZ::SystemAllocator, 0);
ApplicationMac();
~ApplicationMac() override;
////////////////////////////////////////////////////////////////////////////////////////////
// DarwinLifecycleEvents
void OnDidResignActive() override; // Constrain
void OnDidBecomeActive() override; // Unconstrain
void OnDidHide() override; // Suspend
void OnDidUnhide() override; // Resume
////////////////////////////////////////////////////////////////////////////////////////////
// Application::Implementation
void PumpSystemEventLoopOnce() override;
void PumpSystemEventLoopUntilEmpty() override;
protected:
bool ProcessNextSystemEvent(); // Returns true if an event was processed, false otherwise
private:
ApplicationLifecycleEvents::Event m_lastEvent;
id m_notificationObserver;
};
////////////////////////////////////////////////////////////////////////////////////////////////
// Ideally this class would be defined using the standard @interface / @imlementation keywords,
// but an Objective-C class defined in a static lib linked by multiple dynamic libs results in
// runtime warnings, because each dynamic lib resgisters a new version of the exact same class:
//
// "Class X is implemented in both A and B. One of the two will be used. Which one is undefined."
//
// To get around this absurdity we're just defining the Objective-C class dynamically at runtime.
////////////////////////////////////////////////////////////////////////////////////////////////
class ApplicationNotificationObserver
{
public:
////////////////////////////////////////////////////////////////////////////////////////////
//! Load the Objective-C class. Will be created and registered with the Objective-C runtime,
//! unless this has already been done, in which case it will simply be returned immediately.
//! \return The Objective-C class that defines our ApplicationNotificationObserver class
static Class LoadClassType();
////////////////////////////////////////////////////////////////////////////////////////////
//! Resgister an instance of this class for application notifications
//! \param[in] self The instance of this class to register for application notifications
static void RegisterForNotifications(id self);
////////////////////////////////////////////////////////////////////////////////////////////
//! Deresgister an instance of this class for application notifications
//! \param[in] self The instance of this class to deregister for application notifications
static void DeregisterForNotifications(id self);
private:
////////////////////////////////////////////////////////////////////////////////////////////
//! The Objective-C method implementation
///@{
static void OnWillResignActive(id self, SEL methodSelector, NSNotification* notification);
static void OnDidResignActive(id self, SEL methodSelector, NSNotification* notification);
static void OnWillBecomeActive(id self, SEL methodSelector, NSNotification* notification);
static void OnDidBecomeActive(id self, SEL methodSelector, NSNotification* notification);
static void OnWillHide(id self, SEL methodSelector, NSNotification* notification);
static void OnDidHide(id self, SEL methodSelector, NSNotification* notification);
static void OnWillUnhide(id self, SEL methodSelector, NSNotification* notification);
static void OnDidUnhide(id self, SEL methodSelector, NSNotification* notification);
static void OnWillTerminate(id self, SEL methodSelector, NSNotification* notification);
///@}
////////////////////////////////////////////////////////////////////////////////////////////
//! The Objective-C method selector
//! \return The Objective-C method implementation
///@{
static SEL s_applicationWillResignActiveSelector;
static SEL s_applicationDidResignActiveSelector;
static SEL s_applicationWillBecomeActiveSelector;
static SEL s_applicationDidBecomeActiveSelector;
static SEL s_applicationWillHideSelector;
static SEL s_applicationDidHideSelector;
static SEL s_applicationWillUnhideSelector;
static SEL s_applicationDidUnhideSelector;
static SEL s_applicationWillTerminateSelector;
///@}
////////////////////////////////////////////////////////////////////////////////////////////
//! The Objective-C class name
static const char* s_className;
};
////////////////////////////////////////////////////////////////////////////////////////////////
Application::Implementation* Application::Implementation::Create()
{
return aznew ApplicationMac();
}
////////////////////////////////////////////////////////////////////////////////////////////////
ApplicationMac::ApplicationMac()
: m_lastEvent(ApplicationLifecycleEvents::Event::None)
{
DarwinLifecycleEvents::Bus::Handler::BusConnect();
m_notificationObserver = [[ApplicationNotificationObserver::LoadClassType() alloc] init];
ApplicationNotificationObserver::RegisterForNotifications(m_notificationObserver);
}
////////////////////////////////////////////////////////////////////////////////////////////////
ApplicationMac::~ApplicationMac()
{
ApplicationNotificationObserver::DeregisterForNotifications(m_notificationObserver);
[m_notificationObserver release];
m_notificationObserver = nullptr;
DarwinLifecycleEvents::Bus::Handler::BusDisconnect();
}
////////////////////////////////////////////////////////////////////////////////////////////////
void ApplicationMac::OnDidResignActive()
{
EBUS_EVENT(ApplicationLifecycleEvents::Bus, OnApplicationConstrained, m_lastEvent);
m_lastEvent = ApplicationLifecycleEvents::Event::Constrain;
}
////////////////////////////////////////////////////////////////////////////////////////////////
void ApplicationMac::OnDidBecomeActive()
{
EBUS_EVENT(ApplicationLifecycleEvents::Bus, OnApplicationUnconstrained, m_lastEvent);
m_lastEvent = ApplicationLifecycleEvents::Event::Unconstrain;
}
////////////////////////////////////////////////////////////////////////////////////////////////
void ApplicationMac::OnDidHide()
{
EBUS_EVENT(ApplicationLifecycleEvents::Bus, OnApplicationSuspended, m_lastEvent);
m_lastEvent = ApplicationLifecycleEvents::Event::Suspend;
}
////////////////////////////////////////////////////////////////////////////////////////////////
void ApplicationMac::OnDidUnhide()
{
EBUS_EVENT(ApplicationLifecycleEvents::Bus, OnApplicationResumed, m_lastEvent);
m_lastEvent = ApplicationLifecycleEvents::Event::Resume;
}
////////////////////////////////////////////////////////////////////////////////////////////////
void ApplicationMac::PumpSystemEventLoopOnce()
{
ProcessNextSystemEvent();
}
////////////////////////////////////////////////////////////////////////////////////////////////
void ApplicationMac::PumpSystemEventLoopUntilEmpty()
{
bool eventProcessed = false;
do
{
eventProcessed = ProcessNextSystemEvent();
}
while (eventProcessed);
}
////////////////////////////////////////////////////////////////////////////////////////////////
bool ApplicationMac::ProcessNextSystemEvent()
{
@autoreleasepool
{
NSEvent* event = [NSApp nextEventMatchingMask: NSEventMaskAny
untilDate: [NSDate distantPast]
inMode: NSDefaultRunLoopMode
dequeue: YES];
if (event != nil)
{
RawInputNotificationBusMac::Broadcast(&RawInputNotificationsMac::OnRawInputEvent, event);
[NSApp sendEvent: event];
return true;
}
else
{
return false;
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
const char* ApplicationNotificationObserver::s_className = "AzFrameworkApplicationNotificationObserver";
SEL ApplicationNotificationObserver::s_applicationWillResignActiveSelector = @selector(applicationWillResignActive:);
SEL ApplicationNotificationObserver::s_applicationDidResignActiveSelector = @selector(applicationDidResignActive:);
SEL ApplicationNotificationObserver::s_applicationWillBecomeActiveSelector = @selector(applicationWillBecomeActive:);
SEL ApplicationNotificationObserver::s_applicationDidBecomeActiveSelector = @selector(applicationDidBecomeActive:);
SEL ApplicationNotificationObserver::s_applicationWillHideSelector = @selector(applicationWillHide:);
SEL ApplicationNotificationObserver::s_applicationDidHideSelector = @selector(applicationDidHide:);
SEL ApplicationNotificationObserver::s_applicationWillUnhideSelector = @selector(applicationWillUnhide:);
SEL ApplicationNotificationObserver::s_applicationDidUnhideSelector = @selector(applicationDidUnhide:);
SEL ApplicationNotificationObserver::s_applicationWillTerminateSelector = @selector(applicationWillTerminate:);
////////////////////////////////////////////////////////////////////////////////////////////////
Class ApplicationNotificationObserver::LoadClassType()
{
// Check if the class type already exists
Class classType = NSClassFromString([NSString stringWithUTF8String: s_className]);
if (classType != nil)
{
// We've already called this function and created/registered the class type below
return classType;
}
// Get the argument types string for a notification observer method
Method notificationMethod = class_getInstanceMethod([NSNotificationCenter class],
@selector(postNotification:));
const char* notificationMethodArgumentTypes = method_getTypeEncoding(notificationMethod);
// Create the class type
classType = objc_allocateClassPair([NSObject class], s_className, 0);
// Add all the class instance methods
class_addMethod(classType,
s_applicationWillResignActiveSelector,
(IMP)OnWillResignActive,
notificationMethodArgumentTypes);
class_addMethod(classType,
s_applicationDidResignActiveSelector,
(IMP)OnDidResignActive,
notificationMethodArgumentTypes);
class_addMethod(classType,
s_applicationWillBecomeActiveSelector,
(IMP)OnWillBecomeActive,
notificationMethodArgumentTypes);
class_addMethod(classType,
s_applicationDidBecomeActiveSelector,
(IMP)OnDidBecomeActive,
notificationMethodArgumentTypes);
class_addMethod(classType,
s_applicationWillHideSelector,
(IMP)OnWillHide,
notificationMethodArgumentTypes);
class_addMethod(classType,
s_applicationDidHideSelector,
(IMP)OnDidHide,
notificationMethodArgumentTypes);
class_addMethod(classType,
s_applicationWillUnhideSelector,
(IMP)OnWillUnhide,
notificationMethodArgumentTypes);
class_addMethod(classType,
s_applicationDidUnhideSelector,
(IMP)OnDidUnhide,
notificationMethodArgumentTypes);
class_addMethod(classType,
s_applicationWillTerminateSelector,
(IMP)OnWillTerminate,
notificationMethodArgumentTypes);
// Register the class type and return it
objc_registerClassPair(classType);
return classType;
}
////////////////////////////////////////////////////////////////////////////////////////////////
void ApplicationNotificationObserver::RegisterForNotifications(id self)
{
NSNotificationCenter* defaultNotificationCenter = [NSNotificationCenter defaultCenter];
if (!defaultNotificationCenter)
{
return;
}
[defaultNotificationCenter addObserver: self
selector: s_applicationWillResignActiveSelector
name: NSApplicationWillResignActiveNotification
object: nil];
[defaultNotificationCenter addObserver: self
selector: s_applicationDidResignActiveSelector
name: NSApplicationDidResignActiveNotification
object: nil];
[defaultNotificationCenter addObserver: self
selector: s_applicationWillBecomeActiveSelector
name: NSApplicationWillBecomeActiveNotification
object: nil];
[defaultNotificationCenter addObserver: self
selector: s_applicationDidBecomeActiveSelector
name: NSApplicationDidBecomeActiveNotification
object: nil];
[defaultNotificationCenter addObserver: self
selector: s_applicationWillHideSelector
name: NSApplicationWillHideNotification
object: nil];
[defaultNotificationCenter addObserver: self
selector: s_applicationDidHideSelector
name: NSApplicationDidHideNotification
object: nil];
[defaultNotificationCenter addObserver: self
selector: s_applicationWillUnhideSelector
name: NSApplicationWillUnhideNotification
object: nil];
[defaultNotificationCenter addObserver: self
selector: s_applicationDidUnhideSelector
name: NSApplicationDidUnhideNotification
object: nil];
[defaultNotificationCenter addObserver: self
selector: s_applicationWillTerminateSelector
name: NSApplicationWillTerminateNotification
object: nil];
}
////////////////////////////////////////////////////////////////////////////////////////////////
void ApplicationNotificationObserver::DeregisterForNotifications(id self)
{
[[NSNotificationCenter defaultCenter] removeObserver: self];
}
////////////////////////////////////////////////////////////////////////////////////////////////
void ApplicationNotificationObserver::OnWillResignActive(id, SEL, NSNotification*)
{
DarwinLifecycleEvents::Bus::Broadcast(&DarwinLifecycleEvents::OnWillResignActive);
}
////////////////////////////////////////////////////////////////////////////////////////////////
void ApplicationNotificationObserver::OnDidResignActive(id, SEL, NSNotification*)
{
DarwinLifecycleEvents::Bus::Broadcast(&DarwinLifecycleEvents::OnDidResignActive);
}
////////////////////////////////////////////////////////////////////////////////////////////////
void ApplicationNotificationObserver::OnWillBecomeActive(id, SEL, NSNotification*)
{
DarwinLifecycleEvents::Bus::Broadcast(&DarwinLifecycleEvents::OnWillBecomeActive);
}
////////////////////////////////////////////////////////////////////////////////////////////////
void ApplicationNotificationObserver::OnDidBecomeActive(id, SEL, NSNotification*)
{
DarwinLifecycleEvents::Bus::Broadcast(&DarwinLifecycleEvents::OnDidBecomeActive);
}
////////////////////////////////////////////////////////////////////////////////////////////////
void ApplicationNotificationObserver::OnWillHide(id, SEL, NSNotification*)
{
DarwinLifecycleEvents::Bus::Broadcast(&DarwinLifecycleEvents::OnWillHide);
}
////////////////////////////////////////////////////////////////////////////////////////////////
void ApplicationNotificationObserver::OnDidHide(id, SEL, NSNotification*)
{
DarwinLifecycleEvents::Bus::Broadcast(&DarwinLifecycleEvents::OnDidHide);
}
////////////////////////////////////////////////////////////////////////////////////////////////
void ApplicationNotificationObserver::OnWillUnhide(id, SEL, NSNotification*)
{
DarwinLifecycleEvents::Bus::Broadcast(&DarwinLifecycleEvents::OnWillUnhide);
}
////////////////////////////////////////////////////////////////////////////////////////////////
void ApplicationNotificationObserver::OnDidUnhide(id, SEL, NSNotification*)
{
DarwinLifecycleEvents::Bus::Broadcast(&DarwinLifecycleEvents::OnDidUnhide);
}
////////////////////////////////////////////////////////////////////////////////////////////////
void ApplicationNotificationObserver::OnWillTerminate(id, SEL, NSNotification*)
{
DarwinLifecycleEvents::Bus::Broadcast(&DarwinLifecycleEvents::OnWillTerminate);
}
} // namespace AzFramework
@@ -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,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_Mac.h>
@@ -0,0 +1,51 @@
/*
* 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/IO/Path/Path.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include <sys/types.h>
#include <unistd.h>
namespace AzFramework::AssetSystem::Platform
{
void AllowAssetProcessorToForeground()
{}
bool LaunchAssetProcessor(AZStd::string_view executableDirectory, AZStd::string_view appRoot,
AZStd::string_view gameProjectName)
{
AZ::IO::FixedMaxPath assetProcessorPath{ executableDirectory };
// In Mac the Editor and game is within a bundle, so the path to the sibling app
// has to go up from the Contents/MacOS folder the binary is in
assetProcessorPath /= "../../../AssetProcessor.app";
assetProcessorPath = assetProcessorPath.LexicallyNormal();
auto fullLaunchCommand = AZ::IO::FixedMaxPathString::format(R"(open -g "%s" --args --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;
fullLaunchCommand += '"';
}
// Add the active game project to the launch command if not empty
if (!gameProjectName.empty())
{
fullLaunchCommand += R"( --gameFolder=")";
fullLaunchCommand += gameProjectName;
fullLaunchCommand += '"';
}
return system(fullLaunchCommand.c_str()) == 0;
}
}
@@ -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 (true)
#define AZ_TRAIT_AZFRAMEWORK_BOOTSTRAP_CFG_CURRENT_PLATFORM "osx"
#define AZ_TRAIT_AZFRAMEWORK_USE_C11_LOCALTIME_S 0
#define AZ_TRAIT_AZFRAMEWORK_USE_ERRNO_T_TYPEDEF 0
#define AZ_TRAIT_AZFRAMEWORK_USE_POSIX_LOCALTIME_R 1
@@ -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_Mac.h>
@@ -0,0 +1,55 @@
/*
* 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>
@class NSEvent;
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace AzFramework
{
////////////////////////////////////////////////////////////////////////////////////////////////
//! EBus interface used to listen for raw Mac input events broadcast by the system. Applications
//! that want raw Mac events to be processed by the AzFramework input system must broadcast all
//! events received when pumping the NSEvent loop, which is the lowest level we can access input.
//!
//! It's possible to receive multiple events per button/key per frame and (depending on how the
//! NSEvent event loop is pumped) it is also possible that events could be sent from any thread,
//! however it is assumed they'll always be dispatched on the main thread which is the standard.
//!
//! This EBus is intended primarily for the AzFramework input system to process Mac input 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 raw Mac events.
class RawInputNotificationsMac : 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 ~RawInputNotificationsMac() = default;
////////////////////////////////////////////////////////////////////////////////////////////
//! Process raw input events (assumed to be dispatched on the main thread)
//! \param[in] nsEvent The raw event data
virtual void OnRawInputEvent(const NSEvent* /*nsEvent*/) = 0;
};
using RawInputNotificationBusMac = AZ::EBus<RawInputNotificationsMac>;
} // 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/Input/Buses/Notifications/RawInputNotificationBus_Mac.h>
@@ -0,0 +1,797 @@
/*
* 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/Devices/Keyboard/InputDeviceKeyboard.h>
#include <AzFramework/Input/Buses/Notifications/RawInputNotificationBus_Platform.h>
#include <AppKit/NSEvent.h>
#include <AppKit/NSView.h>
#include <AppKit/NSWindow.h>
#include <Carbon/Carbon.h>
#include <objc/runtime.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
// Ideally this would all be done by simply sub-classing NSTextView, but we've been forced to resort
// to these shenanigans because the objective-c runtime deems a class defined in a static lib should
// generate the following warning if that static lib happens to be used by multiple dynamic libs:
//
// "Class X is implemented in both A and B. One of the two will be used. Which one is undefined."
//
// To get around this absurdity we're using method swizzling to hook into two NSResponder methods we
// can then customize by using EBus to forward them to our InputDeviceKeyboardMac instance, which in
// turn will either intercept the calls to implement our custom logic if it owns the NSResponder, or
// return false if it does not own the NSResponder so that we'll invoke the original implementation.
//
// One advantage to all this is that we don't need to add anything to the view hierarchy, instead we
// just create a dummy NSView and call makeFirstResponder then interpretKeyEvents process text input.
namespace
{
////////////////////////////////////////////////////////////////////////////////////////////////
//! Sets the implementation of an objectice-c instance method
//! \param[in] selector The selector used to invoke the method
//! \param[in] newImplementation The new implementation of the method to set
//! \return The existing implementation if it differs from newImplementation, nullptr otherwise
IMP SetInstanceMethodImplementaion(Class classType, SEL selector, IMP newImplementation)
{
Method instanceMethod = class_getInstanceMethod(classType, selector);
if (!instanceMethod)
{
AZ_Warning("SetInstanceMethodImplementaion", false, "Instance method not found");
return nullptr;
}
if (method_getImplementation(instanceMethod) == newImplementation)
{
return nullptr;
}
return method_setImplementation(instanceMethod, newImplementation);
}
////////////////////////////////////////////////////////////////////////////////////////////////
//! EBus interface used to listen for NSResponder method calls that have been intercepted
class NSResponderMethodHookNotifications : public AZ::EBusTraits
{
public:
////////////////////////////////////////////////////////////////////////////////////////////
//! EBus Trait: NSResponder method hook notifications are addressed to a single address
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
////////////////////////////////////////////////////////////////////////////////////////////
//! EBus Trait: NSResponder method hook notifications can be handled by multiple listeners
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
protected:
////////////////////////////////////////////////////////////////////////////////////////////
//! Sent when a call to the NSResponder::insertText method has been intercepted
//! \param[in] responder The NSResponder that the original method was invoked on
//! \param[in] textString The text to insert (could be an NString or an NSAttributedString)
//! \return True if the method call was handled, false otherwise
virtual bool InsertText(NSResponder* responder, id textString) = 0;
////////////////////////////////////////////////////////////////////////////////////////////
//! Sent when a call to the NSResponder::noResponderFor method has been intercepted
//! \param[in] responder The NSResponder that the original method was invoked on
//! \param[in] eventSelector The event selector that was sent to the original message
//! \return True if the method call was handled, false otherwise
virtual bool NoResponderFor(NSResponder* responder, SEL eventSelector) = 0;
////////////////////////////////////////////////////////////////////////////////////////////
//! Sent when a call to the NSResponder::doCommandBySelector method has been intercepted
//! \param[in] responder The NSResponder that the original method was invoked on
//! \param[in] commandSelector The command selector that was sent to the original message
//! \return True if the method call was handled, false otherwise
virtual bool DoCommandBySelector(NSResponder* responder, SEL commandSelector) = 0;
////////////////////////////////////////////////////////////////////////////////////////////
//! Call to insert the custom method hooks into the NSResponder class implementation
static void InsertHooks();
////////////////////////////////////////////////////////////////////////////////////////////
//! Call to remove the custom method hooks from the NSResponder class implementation
static void RemoveHooks();
private:
////////////////////////////////////////////////////////////////////////////////////////////
//! Custom implementation of NSResponder::insertText:
//! \ref NSResponder::insertText:
static void InsertTextHook(NSResponder* responder,
SEL methodSelector,
id textString);
////////////////////////////////////////////////////////////////////////////////////////////
//! Custom implementation of NSResponder::noResponderFor:
//! \ref NSResponder::noResponderFor:
static void NoResponderForHook(NSResponder* responder,
SEL methodSelector,
SEL eventSelector);
////////////////////////////////////////////////////////////////////////////////////////////
//! Custom implementation of NSResponder::doCommandBySelector:
//! \ref NSResponder::doCommandBySelector:
static void DoCommandBySelectorHook(NSResponder* responder,
SEL methodSelector,
SEL commandSelector);
using imp_redirector = void (*)(NSResponder* responder, SEL methodSelector,id textString);
using cmd_redirector = void (*)(NSResponder* responder, SEL previousSelector,SEL newSelector);
////////////////////////////////////////////////////////////////////////////////////////////
//! Pointer to the default implementation of the NSResponder::insertText method
static IMP s_defaultInsertTextImplementation;
////////////////////////////////////////////////////////////////////////////////////////////
//! Pointer to the default implementation of the NSResponder::noResponderFor method
static IMP s_defaultNoResponderForImplementation;
////////////////////////////////////////////////////////////////////////////////////////////
//! Pointer to the default implementation of the NSResponder::doCommandBySelector method
static IMP s_defaultDoCommandBySelectorImplementation;
};
using NSResponderMethodHookNotificationBus = AZ::EBus<NSResponderMethodHookNotifications>;
////////////////////////////////////////////////////////////////////////////////////////////////
IMP NSResponderMethodHookNotifications::s_defaultInsertTextImplementation = nullptr;
////////////////////////////////////////////////////////////////////////////////////////////////
IMP NSResponderMethodHookNotifications::s_defaultNoResponderForImplementation = nullptr;
////////////////////////////////////////////////////////////////////////////////////////////////
IMP NSResponderMethodHookNotifications::s_defaultDoCommandBySelectorImplementation = nullptr;
////////////////////////////////////////////////////////////////////////////////////////////////
void NSResponderMethodHookNotifications::InsertHooks()
{
// Switch the imlplementation of NSResponder::insertText with our custom one,
// storing the original default implementation so that it can be restored later.
s_defaultInsertTextImplementation =
SetInstanceMethodImplementaion([NSResponder class],
@selector(insertText:),
(IMP)InsertTextHook);
// Switch the imlplementation of NSResponder::noResponderFor with our custom one,
// storing the original default implementation so that it can be restored later.
s_defaultNoResponderForImplementation =
SetInstanceMethodImplementaion([NSResponder class],
@selector(noResponderFor:),
(IMP)NoResponderForHook);
// Switch the imlplementation of NSResponder::doCommandBySelector with our custom one,
// storing the original default implementation so that it can be restored later.
s_defaultDoCommandBySelectorImplementation =
SetInstanceMethodImplementaion([NSResponder class],
@selector(doCommandBySelector:),
(IMP)DoCommandBySelectorHook);
}
////////////////////////////////////////////////////////////////////////////////////////////////
void NSResponderMethodHookNotifications::RemoveHooks()
{
// Restore the default imlplementation of NSResponder::doCommandBySelector.
SetInstanceMethodImplementaion([NSResponder class],
@selector(doCommandBySelector:),
s_defaultDoCommandBySelectorImplementation);
s_defaultDoCommandBySelectorImplementation = nullptr;
// Restore the default imlplementation of NSResponder::noResponderFor.
SetInstanceMethodImplementaion([NSResponder class],
@selector(noResponderFor:),
s_defaultNoResponderForImplementation);
s_defaultNoResponderForImplementation = nullptr;
// Restore the default imlplementation of NSResponder::insertText.
SetInstanceMethodImplementaion([NSResponder class],
@selector(insertText:),
s_defaultInsertTextImplementation);
s_defaultInsertTextImplementation = nullptr;
}
////////////////////////////////////////////////////////////////////////////////////////////////
void NSResponderMethodHookNotifications::InsertTextHook(NSResponder* responder,
SEL methodSelector,
id textString)
{
bool handled = false;
NSResponderMethodHookNotificationBus::BroadcastResult(
handled,
&NSResponderMethodHookNotifications::InsertText,
responder,
textString);
if (!handled && s_defaultInsertTextImplementation)
{
reinterpret_cast<imp_redirector>(s_defaultInsertTextImplementation)(responder, methodSelector, textString);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
void NSResponderMethodHookNotifications::NoResponderForHook(NSResponder* responder,
SEL methodSelector,
SEL eventSelector)
{
bool handled = false;
NSResponderMethodHookNotificationBus::BroadcastResult(
handled,
&NSResponderMethodHookNotifications::NoResponderFor,
responder,
eventSelector);
if (!handled && s_defaultNoResponderForImplementation)
{
reinterpret_cast<cmd_redirector>(s_defaultNoResponderForImplementation)(responder, methodSelector, eventSelector);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
void NSResponderMethodHookNotifications::DoCommandBySelectorHook(NSResponder* responder,
SEL methodSelector,
SEL commandSelector)
{
bool handled = false;
NSResponderMethodHookNotificationBus::BroadcastResult(
handled,
&NSResponderMethodHookNotifications::DoCommandBySelector,
responder,
commandSelector);
if (!handled && s_defaultDoCommandBySelectorImplementation)
{
reinterpret_cast<cmd_redirector>(s_defaultDoCommandBySelectorImplementation)(responder, methodSelector, commandSelector);
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace
{
using namespace AzFramework;
////////////////////////////////////////////////////////////////////////////////////////////////
// Table of key ids indexed by their Mac key code
const AZStd::array<const InputChannelId*, 128> InputChannelIdByKeyCodeTable =
{{
&InputDeviceKeyboard::Key::AlphanumericA, // 0x00 kVK_ANSI_A
&InputDeviceKeyboard::Key::AlphanumericS, // 0x01 kVK_ANSI_S
&InputDeviceKeyboard::Key::AlphanumericD, // 0x02 kVK_ANSI_D
&InputDeviceKeyboard::Key::AlphanumericF, // 0x03 kVK_ANSI_F
&InputDeviceKeyboard::Key::AlphanumericH, // 0x04 kVK_ANSI_H
&InputDeviceKeyboard::Key::AlphanumericG, // 0x05 kVK_ANSI_G
&InputDeviceKeyboard::Key::AlphanumericZ, // 0x06 kVK_ANSI_Z
&InputDeviceKeyboard::Key::AlphanumericX, // 0x07 kVK_ANSI_X
&InputDeviceKeyboard::Key::AlphanumericC, // 0x08 kVK_ANSI_C
&InputDeviceKeyboard::Key::AlphanumericV, // 0x09 kVK_ANSI_V
&InputDeviceKeyboard::Key::SupplementaryISO, // 0x0A kVK_ISO_Section
&InputDeviceKeyboard::Key::AlphanumericB, // 0x0B kVK_ANSI_B
&InputDeviceKeyboard::Key::AlphanumericQ, // 0x0C kVK_ANSI_Q
&InputDeviceKeyboard::Key::AlphanumericW, // 0x0D kVK_ANSI_W
&InputDeviceKeyboard::Key::AlphanumericE, // 0x0E kVK_ANSI_E
&InputDeviceKeyboard::Key::AlphanumericR, // 0x0F kVK_ANSI_R
&InputDeviceKeyboard::Key::AlphanumericY, // 0x10 kVK_ANSI_Y
&InputDeviceKeyboard::Key::AlphanumericT, // 0x11 kVK_ANSI_T
&InputDeviceKeyboard::Key::Alphanumeric1, // 0x12 kVK_ANSI_1
&InputDeviceKeyboard::Key::Alphanumeric2, // 0x13 kVK_ANSI_2
&InputDeviceKeyboard::Key::Alphanumeric3, // 0x14 kVK_ANSI_3
&InputDeviceKeyboard::Key::Alphanumeric4, // 0x15 kVK_ANSI_4
&InputDeviceKeyboard::Key::Alphanumeric6, // 0x16 kVK_ANSI_6
&InputDeviceKeyboard::Key::Alphanumeric5, // 0x17 kVK_ANSI_5
&InputDeviceKeyboard::Key::PunctuationEquals, // 0x18 kVK_ANSI_Equal
&InputDeviceKeyboard::Key::Alphanumeric9, // 0x19 kVK_ANSI_9
&InputDeviceKeyboard::Key::Alphanumeric7, // 0x1A kVK_ANSI_7
&InputDeviceKeyboard::Key::PunctuationHyphen, // 0x1B kVK_ANSI_Minus
&InputDeviceKeyboard::Key::Alphanumeric8, // 0x1C kVK_ANSI_8
&InputDeviceKeyboard::Key::Alphanumeric0, // 0x1D kVK_ANSI_0
&InputDeviceKeyboard::Key::PunctuationBracketR, // 0x1E kVK_ANSI_RightBracket
&InputDeviceKeyboard::Key::AlphanumericO, // 0x1F kVK_ANSI_O
&InputDeviceKeyboard::Key::AlphanumericU, // 0x20 kVK_ANSI_U
&InputDeviceKeyboard::Key::PunctuationBracketL, // 0x21 kVK_ANSI_LeftBracket
&InputDeviceKeyboard::Key::AlphanumericI, // 0x22 kVK_ANSI_I
&InputDeviceKeyboard::Key::AlphanumericP, // 0x23 kVK_ANSI_P
&InputDeviceKeyboard::Key::EditEnter, // 0x24 kVK_Return
&InputDeviceKeyboard::Key::AlphanumericL, // 0x25 kVK_ANSI_L
&InputDeviceKeyboard::Key::AlphanumericJ, // 0x26 kVK_ANSI_J
&InputDeviceKeyboard::Key::PunctuationApostrophe, // 0x27 kVK_ANSI_Quote
&InputDeviceKeyboard::Key::AlphanumericK, // 0x28 kVK_ANSI_K
&InputDeviceKeyboard::Key::PunctuationSemicolon, // 0x29 kVK_ANSI_Semicolon
&InputDeviceKeyboard::Key::PunctuationBackslash, // 0x2A kVK_ANSI_Backslash
&InputDeviceKeyboard::Key::PunctuationComma, // 0x2B kVK_ANSI_Comma
&InputDeviceKeyboard::Key::PunctuationSlash, // 0x2C kVK_ANSI_Slash
&InputDeviceKeyboard::Key::AlphanumericN, // 0x2D kVK_ANSI_N
&InputDeviceKeyboard::Key::AlphanumericM, // 0x2E kVK_ANSI_M
&InputDeviceKeyboard::Key::PunctuationPeriod, // 0x2F kVK_ANSI_Period
&InputDeviceKeyboard::Key::EditTab, // 0x30 kVK_Tab
&InputDeviceKeyboard::Key::EditSpace, // 0x31 kVK_Space
&InputDeviceKeyboard::Key::PunctuationTilde, // 0x32 kVK_ANSI_Grave
&InputDeviceKeyboard::Key::EditBackspace, // 0x33 kVK_Delete
nullptr, // 0x34 ?
&InputDeviceKeyboard::Key::Escape, // 0x35 kVK_Escape
&InputDeviceKeyboard::Key::ModifierSuperR, // 0x36 kVK_RightCommand
&InputDeviceKeyboard::Key::ModifierSuperL, // 0x37 kVK_Command
&InputDeviceKeyboard::Key::ModifierShiftL, // 0x38 kVK_Shift
&InputDeviceKeyboard::Key::EditCapsLock, // 0x39 kVK_CapsLock
&InputDeviceKeyboard::Key::ModifierAltL, // 0x3A kVK_Option
&InputDeviceKeyboard::Key::ModifierCtrlL, // 0x3B kVK_Control
&InputDeviceKeyboard::Key::ModifierShiftR, // 0x3C kVK_RightShift
&InputDeviceKeyboard::Key::ModifierAltR, // 0x3D kVK_RightOption
&InputDeviceKeyboard::Key::ModifierCtrlR, // 0x3E kVK_RightControl
nullptr, // 0x3F kVK_Function
&InputDeviceKeyboard::Key::Function17, // 0x40 kVK_F17
&InputDeviceKeyboard::Key::NumPadDecimal, // 0x41 kVK_ANSI_KeypadDecimal
nullptr, // 0x42 ?
&InputDeviceKeyboard::Key::NumPadMultiply, // 0x43 kVK_ANSI_KeypadMultiply
nullptr, // 0x44 ?
&InputDeviceKeyboard::Key::NumPadAdd, // 0x45 kVK_ANSI_KeypadPlus
nullptr, // 0x46 ?
&InputDeviceKeyboard::Key::NumLock, // 0x47 kVK_ANSI_KeypadClear
nullptr, // 0x48 kVK_VolumeUp
nullptr, // 0x49 kVK_VolumeDown
nullptr, // 0x4A kVK_Mute
&InputDeviceKeyboard::Key::NumPadDivide, // 0x4B kVK_ANSI_KeypadDivide
&InputDeviceKeyboard::Key::NumPadEnter, // 0x4C kVK_ANSI_KeypadEnter
nullptr, // 0x4D ?
&InputDeviceKeyboard::Key::NumPadSubtract, // 0x4E kVK_ANSI_KeypadMinus
&InputDeviceKeyboard::Key::Function18, // 0x4F kVK_F18
&InputDeviceKeyboard::Key::Function19, // 0x50 kVK_F19
nullptr, // 0x51 kVK_ANSI_KeypadEquals
&InputDeviceKeyboard::Key::NumPad0, // 0x52 kVK_ANSI_Keypad0
&InputDeviceKeyboard::Key::NumPad1, // 0x53 kVK_ANSI_Keypad1
&InputDeviceKeyboard::Key::NumPad2, // 0x54 kVK_ANSI_Keypad2
&InputDeviceKeyboard::Key::NumPad3, // 0x55 kVK_ANSI_Keypad3
&InputDeviceKeyboard::Key::NumPad4, // 0x56 kVK_ANSI_Keypad4
&InputDeviceKeyboard::Key::NumPad5, // 0x57 kVK_ANSI_Keypad5
&InputDeviceKeyboard::Key::NumPad6, // 0x58 kVK_ANSI_Keypad6
&InputDeviceKeyboard::Key::NumPad7, // 0x59 kVK_ANSI_Keypad7
&InputDeviceKeyboard::Key::Function20, // 0x5A kVK_F20
&InputDeviceKeyboard::Key::NumPad8, // 0x5B kVK_ANSI_Keypad8
&InputDeviceKeyboard::Key::NumPad9, // 0x5C kVK_ANSI_Keypad9
nullptr, // 0x5D kVK_JIS_Yen
nullptr, // 0x5E kVK_JIS_Underscore
nullptr, // 0x5F kVK_JIS_KeypadComma
&InputDeviceKeyboard::Key::Function05, // 0x60 kVK_F5
&InputDeviceKeyboard::Key::Function06, // 0x61 kVK_F6
&InputDeviceKeyboard::Key::Function07, // 0x62 kVK_F7
&InputDeviceKeyboard::Key::Function03, // 0x63 kVK_F3
&InputDeviceKeyboard::Key::Function08, // 0x64 kVK_F8
&InputDeviceKeyboard::Key::Function09, // 0x65 kVK_F9
nullptr, // 0x66 kVK_JIS_Eisu
&InputDeviceKeyboard::Key::Function11, // 0x67 kVK_F11
nullptr, // 0x68 kVK_JIS_Kana
&InputDeviceKeyboard::Key::Function13, // 0x69 kVK_F13
&InputDeviceKeyboard::Key::Function16, // 0x6A kVK_F16
&InputDeviceKeyboard::Key::Function14, // 0x6B kVK_F14
nullptr, // 0x6C ?
&InputDeviceKeyboard::Key::Function10, // 0x6D kVK_F10
nullptr, // 0x6E ?
&InputDeviceKeyboard::Key::Function12, // 0x6F kVK_F12
nullptr, // 0x70 ?
&InputDeviceKeyboard::Key::Function15, // 0x71 kVK_F15
nullptr, // 0x72 kVK_Help
&InputDeviceKeyboard::Key::NavigationHome, // 0x73 kVK_Home
&InputDeviceKeyboard::Key::NavigationPageUp, // 0x74 kVK_PageUp
&InputDeviceKeyboard::Key::NavigationDelete, // 0x75 kVK_ForwardDelete
&InputDeviceKeyboard::Key::Function04, // 0x76 kVK_F4
&InputDeviceKeyboard::Key::NavigationEnd, // 0x77 kVK_End
&InputDeviceKeyboard::Key::Function02, // 0x78 kVK_F2
&InputDeviceKeyboard::Key::NavigationPageDown, // 0x79 kVK_PageDown
&InputDeviceKeyboard::Key::Function01, // 0x7A kVK_F1
&InputDeviceKeyboard::Key::NavigationArrowLeft, // 0x7B kVK_LeftArrow
&InputDeviceKeyboard::Key::NavigationArrowRight, // 0x7C kVK_RightArrow
&InputDeviceKeyboard::Key::NavigationArrowDown, // 0x7D kVK_DownArrow
&InputDeviceKeyboard::Key::NavigationArrowUp, // 0x7E kVK_UpArrow
nullptr // 0x7F ?
}};
////////////////////////////////////////////////////////////////////////////////////////////////
// NSEventType enum constant names were changed in macOS 10.12, but our min-spec is still 10.10
#if __MAC_OS_X_VERSION_MAX_ALLOWED < 101200 // __MAC_10_12 may not be defined by all earlier sdks
static const NSEventType NSEventTypeKeyDown = NSKeyDown;
static const NSEventType NSEventTypeKeyUp = NSKeyUp;
static const NSEventType NSEventTypeFlagsChanged = NSFlagsChanged;
// kVK_RightCommand was also added in macOS 10.12
static const int kVK_RightCommand = 0x36;
#endif // __MAC_OS_X_VERSION_MAX_ALLOWED < 101200
}
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace AzFramework
{
////////////////////////////////////////////////////////////////////////////////////////////////
//! Platform specific implementation for Mac keyboard input devices
class InputDeviceKeyboardMac : public InputDeviceKeyboard::Implementation
, public RawInputNotificationBusMac::Handler
, public NSResponderMethodHookNotificationBus::Handler
{
////////////////////////////////////////////////////////////////////////////////////////////
//! Count of the number instances of this class that have been created
static int s_instanceCount;
public:
////////////////////////////////////////////////////////////////////////////////////////////
// Allocator
AZ_CLASS_ALLOCATOR(InputDeviceKeyboardMac, AZ::SystemAllocator, 0);
////////////////////////////////////////////////////////////////////////////////////////////
//! Constructor
//! \param[in] inputDevice Reference to the input device being implemented
InputDeviceKeyboardMac(InputDeviceKeyboard& inputDevice);
////////////////////////////////////////////////////////////////////////////////////////////
//! Destructor
~InputDeviceKeyboardMac() 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::RawInputNotificationsMac::OnRawInputEvent
void OnRawInputEvent(const NSEvent* nsEvent) override;
////////////////////////////////////////////////////////////////////////////////////////////
// \ref NSResponderMethodHookNotifications::InsertText
bool InsertText(NSResponder* responder, id textString) override;
////////////////////////////////////////////////////////////////////////////////////////////
// \ref NSResponderMethodHookNotifications::NoResponderFor
bool NoResponderFor(NSResponder* responder, SEL eventSelector) override;
////////////////////////////////////////////////////////////////////////////////////////////
// \ref NSResponderMethodHookNotifications::DoCommandBySelector
bool DoCommandBySelector(NSResponder* responder, SEL commandSelector) override;
////////////////////////////////////////////////////////////////////////////////////////////
//! Convenience function to queue standard key events processed in OnRawInputEvent
//! \param[in] keyCode The Mac specific key code
//! \param[in] keyState The key state (down or up)
void QueueRawStandardKeyEvent(AZ::u32 keyCode, bool keyState);
////////////////////////////////////////////////////////////////////////////////////////////
//! Convenience function to queue modifier key events processed in OnRawInputEvent
//! \param[in] keyCode The Mac specific key code
//! \param[in] modifierFlags The event's modifier flags
void QueueRawModifierKeyEvent(AZ::u32 keyCode, AZ::u32 modifierFlags);
////////////////////////////////////////////////////////////////////////////////////////////
//! A dummy NSView used to interpret key down events into text
NSView* m_textInterpreterView = nullptr;
////////////////////////////////////////////////////////////////////////////////////////////
//! Has text entry been started?
bool m_hasTextEntryStarted = false;
////////////////////////////////////////////////////////////////////////////////////////////
//! Does the application's main window currently have focus?
bool m_hasFocus = false;
};
////////////////////////////////////////////////////////////////////////////////////////////////
InputDeviceKeyboard::Implementation* InputDeviceKeyboard::Implementation::Create(InputDeviceKeyboard& inputDevice)
{
return aznew InputDeviceKeyboardMac(inputDevice);
}
////////////////////////////////////////////////////////////////////////////////////////////////
int InputDeviceKeyboardMac::s_instanceCount = 0;
////////////////////////////////////////////////////////////////////////////////////////////////
InputDeviceKeyboardMac::InputDeviceKeyboardMac(InputDeviceKeyboard& inputDevice)
: InputDeviceKeyboard::Implementation(inputDevice)
, m_textInterpreterView(nullptr)
, m_hasTextEntryStarted(false)
, m_hasFocus(false)
{
if (s_instanceCount++ == 0)
{
NSResponderMethodHookNotifications::InsertHooks();
}
// Create an NSView that we can call interpretKeyEvents on to process text input.
m_textInterpreterView = [[NSView alloc] initWithFrame: CGRectZero];
RawInputNotificationBusMac::Handler::BusConnect();
NSResponderMethodHookNotificationBus::Handler::BusConnect();
}
////////////////////////////////////////////////////////////////////////////////////////////////
InputDeviceKeyboardMac::~InputDeviceKeyboardMac()
{
NSResponderMethodHookNotificationBus::Handler::BusDisconnect();
RawInputNotificationBusMac::Handler::BusDisconnect();
if (m_textInterpreterView)
{
[m_textInterpreterView release];
m_textInterpreterView = nullptr;
}
if (--s_instanceCount == 0)
{
NSResponderMethodHookNotifications::RemoveHooks();
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
bool InputDeviceKeyboardMac::IsConnected() const
{
// If necessary we may be able to determine the connected state using the I/O Kit HIDManager:
// https://developer.apple.com/library/content/documentation/DeviceDrivers/Conceptual/HID/new_api_10_5/tn2187.html
//
// Doing this may allow (and perhaps even 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
// InputDeviceKeyboardMac::OnRawInputEvent function to filter incoming events by this raw id.
return true;
}
////////////////////////////////////////////////////////////////////////////////////////////////
bool InputDeviceKeyboardMac::HasTextEntryStarted() const
{
return m_hasTextEntryStarted;
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceKeyboardMac::TextEntryStart(const InputTextEntryRequests::VirtualKeyboardOptions&)
{
m_hasTextEntryStarted = true;
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceKeyboardMac::TextEntryStop()
{
m_hasTextEntryStarted = false;
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceKeyboardMac::TickInputDevice()
{
// The event loop has just been pumped in ApplicationRequests::PumpSystemEventLoopUntilEmpty,
// so we now just need to process any raw events that have been queued since the last frame
const bool hadFocus = m_hasFocus;
m_hasFocus = NSApplication.sharedApplication.active;
if (m_hasFocus)
{
// Process raw event queues once each frame while this application's window has focus
ProcessRawEventQueues();
}
else if (hadFocus)
{
// This application's window no longer has focus, process any events that are queued,
// before resetting the state of all this input device's associated input channels.
ProcessRawEventQueues();
ResetInputChannelStates();
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceKeyboardMac::OnRawInputEvent(const NSEvent* nsEvent)
{
if (!NSApplication.sharedApplication.active)
{
return;
}
switch (nsEvent.type)
{
case NSEventTypeKeyDown:
{
// We can ignore repeat events here...
if (!nsEvent.isARepeat)
{
QueueRawStandardKeyEvent(nsEvent.keyCode, true);
}
// ...but they should still generate text.
#if !defined(ALWAYS_DISPATCH_KEYBOARD_TEXT_INPUT)
if (m_hasTextEntryStarted)
#endif // defined(ALWAYS_DISPATCH_KEYBOARD_TEXT_INPUT)
{
// This is important, otherwise interpretKeyEvents may do nothing
[NSApplication.sharedApplication.mainWindow makeFirstResponder: nil];
// Translate key presses into text that will get sent on
// to NSResponderMethodHookNotifications::InsertTextHook
[m_textInterpreterView interpretKeyEvents: [NSArray arrayWithObject: nsEvent]];
if (nsEvent.keyCode == kVK_Delete)
{
// Emulate Windows where the backspace key generates a '\b' character
const AZStd::string textUTF8 = "\b";
QueueRawTextEvent(textUTF8);
}
}
}
break;
case NSEventTypeKeyUp:
{
QueueRawStandardKeyEvent(nsEvent.keyCode, false);
}
break;
case NSEventTypeFlagsChanged:
{
QueueRawModifierKeyEvent(nsEvent.keyCode, nsEvent.modifierFlags);
}
break;
default:
{
// Ignore
}
break;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
bool InputDeviceKeyboardMac::InsertText(NSResponder* responder, id textString)
{
if (responder != m_textInterpreterView)
{
// We don't own the responder that this method was invoked on
return false;
}
if (!NSApplication.sharedApplication.active)
{
// This application is not active
return false;
}
const bool isAttributed = [textString isKindOfClass: [NSAttributedString class]];
const AZStd::string textUTF8 = isAttributed ?
[textString string].UTF8String :
[textString UTF8String];
QueueRawTextEvent(textUTF8);
return true;
}
////////////////////////////////////////////////////////////////////////////////////////////////
bool InputDeviceKeyboardMac::NoResponderFor(NSResponder* /*responder*/, SEL /*eventSelector*/)
{
// Do nothing, but return true so we don't invoke the default behavior that calls NSBeep.
// This method is only ever called on the main NSWindow object, so while it is not ideal
// that we're intercepting methods to an object we don't own it's the only way to ensure
// (in all circumstances) that a beeping sound isn't emitted when pressing keyboard keys.
return true;
}
////////////////////////////////////////////////////////////////////////////////////////////////
bool InputDeviceKeyboardMac::DoCommandBySelector(NSResponder* responder, SEL /*commandSelector*/)
{
// If we own the responder that this method was invoked on return
// true so we don't invoke the default behavior that calls NSBeep
return responder == m_textInterpreterView;
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceKeyboardMac::QueueRawStandardKeyEvent(AZ::u32 keyCode, bool keyState)
{
if ((keyCode == kVK_ISO_Section || keyCode == kVK_ANSI_Grave) &&
KBGetLayoutType(LMGetKbdType()) == kKeyboardISO)
{
// Mac swaps these two key codes for keyboards that use an ISO mechanical layout,
// so we have to swap them back.
keyCode = (kVK_ISO_Section + kVK_ANSI_Grave) - keyCode;
}
const InputChannelId* channelId = (keyCode < InputChannelIdByKeyCodeTable.size()) ?
InputChannelIdByKeyCodeTable[keyCode] : nullptr;
if (channelId)
{
QueueRawKeyEvent(*channelId, keyState);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceKeyboardMac::QueueRawModifierKeyEvent(AZ::u32 keyCode, AZ::u32 modifierFlags)
{
const InputChannelId* channelId = (keyCode < InputChannelIdByKeyCodeTable.size()) ?
InputChannelIdByKeyCodeTable[keyCode] : nullptr;
if (!channelId)
{
return;
}
switch (keyCode)
{
case kVK_Option:
{
QueueRawKeyEvent(*channelId, modifierFlags & NX_DEVICELALTKEYMASK);
}
break;
case kVK_RightOption:
{
QueueRawKeyEvent(*channelId, modifierFlags & NX_DEVICERALTKEYMASK);
}
break;
case kVK_Control:
{
QueueRawKeyEvent(*channelId, modifierFlags & NX_DEVICELCTLKEYMASK);
}
break;
case kVK_RightControl:
{
QueueRawKeyEvent(*channelId, modifierFlags & NX_DEVICERCTLKEYMASK);
}
break;
case kVK_Shift:
{
QueueRawKeyEvent(*channelId, modifierFlags & NX_DEVICELSHIFTKEYMASK);
}
break;
case kVK_RightShift:
{
QueueRawKeyEvent(*channelId, modifierFlags & NX_DEVICERSHIFTKEYMASK);
}
break;
case kVK_Command:
{
QueueRawKeyEvent(*channelId, modifierFlags & NX_DEVICELCMDKEYMASK);
}
break;
case kVK_RightCommand:
{
QueueRawKeyEvent(*channelId, modifierFlags & NX_DEVICERCMDKEYMASK);
}
break;
case kVK_CapsLock:
{
// Caps lock is annoying in that it only reports events on key up (when the state of
// the key changes), and never on key down, making it unlike all other keyboard keys.
// While not ideal, simply sending both 'down' and 'up' events in succession when we
// detect a change to the caps lock modifier works well enough, although it means we
// will never be able to detect if the caps lock key is being held down.
QueueRawKeyEvent(*channelId, true);
QueueRawKeyEvent(*channelId, false);
}
break;
default:
{
// Not a supported modifier key
}
break;
}
}
} // namespace AzFramework
@@ -0,0 +1,666 @@
/*
* 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/Devices/Mouse/InputDeviceMouse.h>
#include <AzFramework/Input/Buses/Notifications/RawInputNotificationBus_Platform.h>
#include <AzFramework/Input/Buses/Requests/InputSystemCursorRequestBus.h>
#include <AzCore/Debug/Trace.h>
#include <AzCore/std/parallel/thread.h>
#include <AppKit/NSApplication.h>
#include <AppKit/NSEvent.h>
#include <AppKit/NSScreen.h>
#include <AppKit/NSView.h>
#include <AppKit/NSWindow.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace
{
////////////////////////////////////////////////////////////////////////////////////////////////
// NSEventType enum constant names were changed in macOS 10.12, but our min-spec is still 10.10
#if __MAC_OS_X_VERSION_MAX_ALLOWED < 101200 // __MAC_10_12 may not be defined by all earlier sdks
static const NSEventType NSEventTypeLeftMouseDown = NSLeftMouseDown;
static const NSEventType NSEventTypeLeftMouseUp = NSLeftMouseUp;
static const NSEventType NSEventTypeRightMouseDown = NSRightMouseDown;
static const NSEventType NSEventTypeRightMouseUp = NSRightMouseUp;
static const NSEventType NSEventTypeOtherMouseDown = NSOtherMouseDown;
static const NSEventType NSEventTypeOtherMouseUp = NSOtherMouseUp;
static const NSEventType NSEventTypeScrollWheel = NSScrollWheel;
static const NSEventType NSEventTypeMouseMoved = NSMouseMoved;
static const NSEventType NSEventTypeLeftMouseDragged = NSLeftMouseDragged;
static const NSEventType NSEventTypeRightMouseDragged = NSRightMouseDragged;
static const NSEventType NSEventTypeOtherMouseDragged = NSOtherMouseDragged;
#endif // __MAC_OS_X_VERSION_MAX_ALLOWED < 101200
////////////////////////////////////////////////////////////////////////////////////////////////
//! Get the main application view that should be used to clip and/or normalize the cursor.
//! \return The NSView that should currently be considered as the applictaion's main view.
NSView* GetSystemCursorMainContentView()
{
void* systemCursorMainContentView = nullptr;
AzFramework::InputSystemCursorConstraintRequestBus::BroadcastResult(
systemCursorMainContentView,
&AzFramework::InputSystemCursorConstraintRequests::GetSystemCursorConstraintWindow);
return systemCursorMainContentView ?
static_cast<NSView*>(systemCursorMainContentView) :
NSApplication.sharedApplication.mainWindow.contentView;
}
////////////////////////////////////////////////////////////////////////////////////////////////
//! Convert point from the global screen co-ordinate space of the Core Graphics Framework to the
//! local co-ordinate space of an NSView. It is assumed the y co-ordinate of the point passed to
//! this function is measured from the top-left corner of the screen, and all necessary flipping
//! operations will be performed internally to ensure the y co-ordinate of the point returned is
//! measured from the top-left corner of the NSView (which may not be expected if then used with
//! an NSView that is not using flipped co-ordinates, so care should be taken using the result).
//! However, the optional 'relativeToScreenTop' arg can instead be set to false to indicate that
//! pointInScreenSpace should be assumed relative to the bottom-left of the screen.
//! \param[in] pointInScreenSpace A point relative to the top-left corner of global screen space
//! \param[in] view The view whose local co-ordinate space the screen point will be converted to
//! \param[in] relativeToScreenTop True if the point is relative to the screen top, false bottom
//! \return The point relative to the top-left corner of the local co-ordinate space of the view
NSPoint ConvertPointFromScreenSpaceToViewSpace(const NSPoint& pointInScreenSpace,
NSView* view,
bool relativeToScreenTop = true)
{
NSRect pointInScreenSpaceAsRect;
pointInScreenSpaceAsRect.size = NSZeroSize;
pointInScreenSpaceAsRect.origin = pointInScreenSpace;
if (relativeToScreenTop)
{
// The AppKit framework measures y co-ordinates from the bottom of the screen so we must
// ensure our point is also relative to the bottom before converting it to window space.
pointInScreenSpaceAsRect.origin.y = NSScreen.mainScreen.frame.size.height - pointInScreenSpace.y;
}
// Convert the point into window space then view space
NSPoint pointInWindowSpace = [view.window convertRectFromScreen: pointInScreenSpaceAsRect].origin;
NSPoint pointInViewSpace = [view convertPoint: pointInWindowSpace fromView: nil];
// Make point relative to the top of the view unless it's already using flipped co-ordinates
if (!view.isFlipped)
{
pointInViewSpace.y = view.frame.size.height - pointInViewSpace.y;
}
return pointInViewSpace;
}
////////////////////////////////////////////////////////////////////////////////////////////////
//! Convert point from the local co-ordinate space of an NSView to the global screen co-ordinate
//! space of the Core Graphics Framework. It is assumed the y co-ordinate of the point passed to
//! this function is measured from the top-left corner of the NSView, and all necessary flipping
//! operations will be performed internally to ensure the y co-ordinate of the point returned is
//! measured from the top-left corner of the screen (as expected by the Core Graphics Framework).
//! \param[in] pointInViewSpace A point relative to the top-left of a view's co-ordinate space
//! \param[in] view The NSView whose local co-ordinate space the point will be converted from
//! \return The point relative to the top-left corner of the screen's global co-ordinate space
NSPoint ConvertPointFromViewSpaceToScreenSpace(NSPoint pointInViewSpace, NSView* view)
{
if (!view.isFlipped)
{
// The AppKit framework measures y co-ordinates from the bottom of the screen, and while
// NSViews can be set to use flipped co-ordinates NSWindows cannot. So before converting
// it to window space we must ensure our point is relative to the view's top-left corner.
pointInViewSpace.y = view.frame.size.height - pointInViewSpace.y;
}
// Convert the point into window space
NSRect pointInWindowSpaceAsRect;
pointInWindowSpaceAsRect.size = NSZeroSize;
pointInWindowSpaceAsRect.origin = [view convertPoint: pointInViewSpace toView: nil];
// Convert the point into screen space, then make it relative to the screen's top-left corner
NSPoint pointInScreenSpace = [view.window convertRectToScreen: pointInWindowSpaceAsRect].origin;
pointInScreenSpace.y = NSScreen.mainScreen.frame.size.height - pointInScreenSpace.y;
return pointInScreenSpace;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace AzFramework
{
////////////////////////////////////////////////////////////////////////////////////////////////
//! Platform specific implementation for Mac mouse input devices
class InputDeviceMouseMac : public InputDeviceMouse::Implementation
, public RawInputNotificationBusMac::Handler
{
public:
////////////////////////////////////////////////////////////////////////////////////////////
// Allocator
AZ_CLASS_ALLOCATOR(InputDeviceMouseMac, AZ::SystemAllocator, 0);
////////////////////////////////////////////////////////////////////////////////////////////
//! Constructor
//! \param[in] inputDevice Reference to the input device being implemented
InputDeviceMouseMac(InputDeviceMouse& inputDevice);
////////////////////////////////////////////////////////////////////////////////////////////
//! Destructor
~InputDeviceMouseMac() override;
////////////////////////////////////////////////////////////////////////////////////////////
//! Access to the event tap that exists while the cursor is constrained
inline CFMachPortRef GetDisabledSystemCursorEventTap() const { return m_disabledSystemCursorEventTap; }
////////////////////////////////////////////////////////////////////////////////////////////
//! Access to the event tap that exists while the cursor is constrained
inline void SetDisabledSystemCursorPosition(const CGPoint& point) { m_disabledSystemCursorPosition = point; }
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::RawInputNotificationsMac::OnRawInputEvent
void OnRawInputEvent(const NSEvent* nsEvent) override;
////////////////////////////////////////////////////////////////////////////////////////////
//! Update the system cursor visibility
void UpdateSystemCursorVisibility();
////////////////////////////////////////////////////////////////////////////////////////////
//! Create an event tap that effectively disables the system cursor, preventing it from
//! being used to select any other application, but remaining visible and moving around
//! the screen under user control with the usual system cursor ballistics being applied.
//! \return True if the event tap was created (or had already been created), false otherwise
bool CreateDisabledSystemCursorEventTap();
////////////////////////////////////////////////////////////////////////////////////////////
//! Destroy the disabled system cursor event tap
void DestroyDisabledSystemCursorEventTap();
////////////////////////////////////////////////////////////////////////////////////////////
// Variables
CFRunLoopSourceRef m_disabledSystemCursorRunLoopSource; //!< The disabled cursor run loop
CFMachPortRef m_disabledSystemCursorEventTap; //!< The disabled cursor event tap
CGPoint m_disabledSystemCursorPosition; //!< The disabled cursor position
SystemCursorState m_systemCursorState; //!< Current system cursor state
bool m_hasFocus; //!< Does application have focus?
};
////////////////////////////////////////////////////////////////////////////////////////////////
InputDeviceMouse::Implementation* InputDeviceMouse::Implementation::Create(InputDeviceMouse& inputDevice)
{
return aznew InputDeviceMouseMac(inputDevice);
}
////////////////////////////////////////////////////////////////////////////////////////////////
InputDeviceMouseMac::InputDeviceMouseMac(InputDeviceMouse& inputDevice)
: InputDeviceMouse::Implementation(inputDevice)
, m_disabledSystemCursorRunLoopSource(nullptr)
, m_disabledSystemCursorEventTap(nullptr)
, m_disabledSystemCursorPosition()
, m_systemCursorState(SystemCursorState::Unknown)
, m_hasFocus(false)
{
RawInputNotificationBusMac::Handler::BusConnect();
}
////////////////////////////////////////////////////////////////////////////////////////////////
InputDeviceMouseMac::~InputDeviceMouseMac()
{
RawInputNotificationBusMac::Handler::BusDisconnect();
// Cleanup system cursor visibility and constraint
DestroyDisabledSystemCursorEventTap();
}
////////////////////////////////////////////////////////////////////////////////////////////////
bool InputDeviceMouseMac::IsConnected() const
{
// If necessary we may be able to determine the connected state using the I/O Kit HIDManager:
// https://developer.apple.com/library/content/documentation/DeviceDrivers/Conceptual/HID/new_api_10_5/tn2187.html
//
// Doing this may allow (and perhaps even 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/InputSystemComponentMac in order to create multiple InputDeviceMouse
// instances (somehow associating each with a raw mouse device id), along with modifying the
// InputDeviceMouseMac::OnRawInputEvent function to filter incoming events by raw device id.
return true;
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceMouseMac::SetSystemCursorState(SystemCursorState systemCursorState)
{
// Because Mac does not provide a way to properly constrain the system cursor inside of a
// window, when it's constrained we're actually just hiding it and modifying the location
// of all incoming mouse events so they can't cause the applications window to lose focus.
// This works fine when the cursor is hidden, provided we normalize its position relative
// to the entire screen (see InputDeviceMouseMac::GetSystemCursorPositionNormalized). But
// when the cursor is constrained and visible, we must manually hide the cursor when it's
// outside the active window (see InputDeviceMouseMac::UpdateSystemCursorVisibility), and
// we must normalize it relative to the active window or else the position will not match
// that of the cursor while it is visible inside the active window. Long story short, the
// ConstrainedAndVisible state on Mac results in a 'dead-zone' where the system is moving
// the cursor outside of the active window, but the position is clamped within the border
// of the active window. This is fine when running in full screen on a single monitor but
// may not provide the greatest user experience in windowed mode or a multi-monitor setup.
AZ_Warning("InputDeviceMouseMac",
systemCursorState != SystemCursorState::ConstrainedAndVisible,
"ConstrainedAndVisible does not work entirely as might be expected on Mac when "
"running in windowed mode or with a multi-monitor setup. If your game needs to "
"support either of these it is recommended you use another system cursor state.");
if (systemCursorState == m_systemCursorState)
{
return;
}
m_systemCursorState = systemCursorState;
UpdateSystemCursorVisibility();
const bool shouldBeDisabled = (m_systemCursorState == SystemCursorState::ConstrainedAndHidden) ||
(m_systemCursorState == SystemCursorState::ConstrainedAndVisible);
if (!shouldBeDisabled)
{
DestroyDisabledSystemCursorEventTap();
return;
}
const bool isDisabled = CreateDisabledSystemCursorEventTap();
if (!isDisabled)
{
AZ_Warning("InputDeviceMouseMac", false, "Failed create event tap; cursor cannot be constrained as requested");
m_systemCursorState = SystemCursorState::UnconstrainedAndVisible;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
SystemCursorState InputDeviceMouseMac::GetSystemCursorState() const
{
return m_systemCursorState;
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceMouseMac::SetSystemCursorPositionNormalized(AZ::Vector2 positionNormalized)
{
CGPoint newPositionInScreenSpace;
if (m_systemCursorState == SystemCursorState::ConstrainedAndHidden)
{
// Because Mac does not provide a way to properly constrain the system cursor inside of
// a window, when it is constrained we are actually just modifying the locations of all
// incoming mouse events so they cannot cause the application window to lose focus. So
// if the cursor is also hidden we simply need to de-normalize the position relative to
// the entire screen. See comment in InputDeviceMouseMac::SetSystemCursorState for more.
NSSize cursorFrameSize = NSScreen.mainScreen.frame.size;
newPositionInScreenSpace.x = positionNormalized.GetX() * cursorFrameSize.width;
newPositionInScreenSpace.y = positionNormalized.GetY() * cursorFrameSize.height;
m_disabledSystemCursorPosition = newPositionInScreenSpace;
}
else
{
// However, if the system cursor is visible or not constrained, we need to de-normalize
// the desired position relative to the content rect of the application's main view and
// then convert it to screen space.
NSView* mainView = GetSystemCursorMainContentView();
NSSize cursorFrameSize = mainView.frame.size;
NSPoint newPositionInViewSpace = NSMakePoint(positionNormalized.GetX() * cursorFrameSize.width,
positionNormalized.GetY() * cursorFrameSize.height);
newPositionInScreenSpace = ConvertPointFromViewSpaceToScreenSpace(newPositionInViewSpace, mainView);
}
CGWarpMouseCursorPosition(newPositionInScreenSpace);
}
////////////////////////////////////////////////////////////////////////////////////////////////
AZ::Vector2 InputDeviceMouseMac::GetSystemCursorPositionNormalized() const
{
// Because Mac does not provide a way to properly constrain the system cursor inside of a
// window, when it's constrained we are actually just disabling it by modifying locations
// of all incoming mouse events so they cannot cause the application window to lose focus.
//
// So if the cursor is also hidden we simply need to normalize the position relative to the
// entire screen, but we must use the disabled position because calling CGEventSetLocation
// (see DisabledSysetmCursorEventTapCallback) results in NSEvent.mouseLocation returning
// the clamped position (along with CGEventGetLocation and CGEventGetUnflippedLocation).
NSSize cursorFrameSize = NSScreen.mainScreen.frame.size;
NSPoint cursorPosition = m_disabledSystemCursorPosition;
if (m_systemCursorState != SystemCursorState::ConstrainedAndHidden)
{
// However, if the system cursor is visible or not constrained, we need to normalize
// the cursor position (which in this case can be obtained by NSEvent.mouseLocation)
// relative to the content rect of the application's main view.
NSView* mainView = GetSystemCursorMainContentView();
cursorFrameSize = mainView.frame.size;
cursorPosition = ConvertPointFromScreenSpaceToViewSpace(NSEvent.mouseLocation,
mainView,
false);
}
// Normalize the cursor position
const float cursorPostionNormalizedX = cursorFrameSize.width != 0.0f ? cursorPosition.x / cursorFrameSize.width : 0.0f;
const float cursorPostionNormalizedY = cursorFrameSize.height != 0.0f ? cursorPosition.y / cursorFrameSize.height : 0.0f;
return AZ::Vector2(cursorPostionNormalizedX, cursorPostionNormalizedY);
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceMouseMac::TickInputDevice()
{
// The event loop has just been pumped in ApplicationRequests::PumpSystemEventLoopUntilEmpty,
// so we now just need to process any raw events that have been queued since the last frame
const bool hadFocus = m_hasFocus;
m_hasFocus = NSApplication.sharedApplication.active;
if (m_hasFocus)
{
// Update the visibility of the system cursor, which unfortunately must be done every
// frame to combat the cursor being displayed by the system under some circumstances.
UpdateSystemCursorVisibility();
// Process raw event queues once each frame while this application's window has focus
ProcessRawEventQueues();
}
else if (hadFocus)
{
// This application's window no longer has focus, process any events that are queued,
// before resetting the state of all this input device's associated input channels.
ProcessRawEventQueues();
ResetInputChannelStates();
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceMouseMac::OnRawInputEvent(const NSEvent* nsEvent)
{
switch (nsEvent.type)
{
// Left button
case NSEventTypeLeftMouseDown:
{
QueueRawButtonEvent(InputDeviceMouse::Button::Left, true);
}
break;
case NSEventTypeLeftMouseUp:
{
QueueRawButtonEvent(InputDeviceMouse::Button::Left, false);
}
break;
// Right button
case NSEventTypeRightMouseDown:
{
QueueRawButtonEvent(InputDeviceMouse::Button::Right, true);
}
break;
case NSEventTypeRightMouseUp:
{
QueueRawButtonEvent(InputDeviceMouse::Button::Right, false);
}
break;
// Middle button
case NSEventTypeOtherMouseDown:
{
QueueRawButtonEvent(InputDeviceMouse::Button::Middle, true);
}
break;
case NSEventTypeOtherMouseUp:
{
QueueRawButtonEvent(InputDeviceMouse::Button::Middle, false);
}
break;
// Scroll wheel
case NSEventTypeScrollWheel:
{
QueueRawMovementEvent(InputDeviceMouse::Movement::Z, nsEvent.scrollingDeltaY);
}
break;
// Mouse movement
case NSEventTypeMouseMoved:
case NSEventTypeLeftMouseDragged:
case NSEventTypeRightMouseDragged:
case NSEventTypeOtherMouseDragged:
{
QueueRawMovementEvent(InputDeviceMouse::Movement::X, nsEvent.deltaX);
QueueRawMovementEvent(InputDeviceMouse::Movement::Y, nsEvent.deltaY);
}
break;
default:
{
// Ignore
}
break;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
bool IsPointInsideMainView(const NSPoint& pointInScreenSpace, bool relativeToTopLeft = true)
{
NSView* mainView = GetSystemCursorMainContentView();
NSPoint pointInViewSpace = ConvertPointFromScreenSpaceToViewSpace(pointInScreenSpace,
mainView,
relativeToTopLeft);
return NSPointInRect(pointInViewSpace, mainView.bounds);
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceMouseMac::UpdateSystemCursorVisibility()
{
bool shouldCursorBeVisible = true;
switch (m_systemCursorState)
{
case SystemCursorState::ConstrainedAndHidden:
{
shouldCursorBeVisible = false;
}
break;
case SystemCursorState::ConstrainedAndVisible:
{
shouldCursorBeVisible = IsPointInsideMainView(m_disabledSystemCursorPosition);
}
break;
case SystemCursorState::UnconstrainedAndHidden:
{
shouldCursorBeVisible = !IsPointInsideMainView(NSEvent.mouseLocation, false);
}
break;
case SystemCursorState::UnconstrainedAndVisible:
case SystemCursorState::Unknown:
{
shouldCursorBeVisible = true;
}
break;
}
// CGCursorIsVisible has been deprecated but still works, and there is no other way to check
// whether the system cursor is currently visible. Calls to CGDisplayHideCursor are meant to
// increment an application specific 'cursor hidden' counter that must be balanced by a call
// to CGDisplayShowCursor, however this doesn't seem to work if the cursor is shown again by
// the system (or another application), which can happen when this application loses focus.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
const bool isCursorVisible = CGCursorIsVisible();
#pragma clang diagnostic pop
if (isCursorVisible && !shouldCursorBeVisible)
{
CGDisplayHideCursor(kCGNullDirectDisplay);
}
else if (!isCursorVisible && shouldCursorBeVisible)
{
CGDisplayShowCursor(kCGNullDirectDisplay);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
CGEventRef DisabledSysetmCursorEventTapCallback(CGEventTapProxy eventTapProxy,
CGEventType eventType,
CGEventRef eventRef,
void* userInfo)
{
InputDeviceMouseMac* inputDeviceMouse = static_cast<InputDeviceMouseMac*>(userInfo);
switch (eventType)
{
case kCGEventTapDisabledByTimeout:
case kCGEventTapDisabledByUserInput:
{
// Re-enable the event tap if it gets disabled (important to do this first)
CGEventTapEnable(inputDeviceMouse->GetDisabledSystemCursorEventTap(), true);
return eventRef;
}
break;
}
if (!NSApplication.sharedApplication.active)
{
// Do nothing if the application is not active
return eventRef;
}
NSView* mainView = GetSystemCursorMainContentView();
if (mainView == nil || mainView.window == nil)
{
// Do nothing if we don't have a main view
return eventRef;
}
// Store the location of the event before it is (potentially) modified below using
// CGEventSetLocation. This is needed so that the un-clamped position can still be
// used to get/set the system cursor position while it is ConstrainedAndHidden.
CGPoint eventLocationRelativeToTopLeft = CGEventGetLocation(eventRef);
inputDeviceMouse->SetDisabledSystemCursorPosition(eventLocationRelativeToTopLeft);
// Get the location of the event relative to the bottom left of the screen, needed
// so that it can be checked against the cursor bounds from this co-ordinate space.
CGPoint eventLocation = CGEventGetUnflippedLocation(eventRef);
// Get the current cursor bounds of the main view, then adjust them to exclude the
// corner radius, whose value was deduced by trial and error because there doesn't
// appear to be any other way to get it (mainView.layer.cornerRadius returns 0.0f).
NSRect cursorBoundsInWindowSpace = [mainView convertRect: mainView.bounds toView: nil];
NSRect cursorBounds = [mainView.window convertRectToScreen: cursorBoundsInWindowSpace];
const float cornerRadius = 2.0f;
cursorBounds = NSInsetRect(cursorBounds, cornerRadius, cornerRadius);
if (NSPointInRect(NSPointFromCGPoint(eventLocation), cursorBounds))
{
// Do nothing if the event occured inside of the application's main view
return eventRef;
}
// Constrain the event location to the cursor bounds.
eventLocation.x = AZ::GetClamp(eventLocation.x, NSMinX(cursorBounds), NSMaxX(cursorBounds));
eventLocation.y = AZ::GetClamp(eventLocation.y, NSMinY(cursorBounds), NSMaxY(cursorBounds));
// Reset the event location after flipping it back to be relative to the top of the screen
eventLocation.y = NSMaxY(NSScreen.mainScreen.frame) - eventLocation.y;
CGEventSetLocation(eventRef, eventLocation);
return eventRef;
}
////////////////////////////////////////////////////////////////////////////////////////////////
bool InputDeviceMouseMac::CreateDisabledSystemCursorEventTap()
{
if (m_disabledSystemCursorEventTap != nullptr)
{
// It has already been created
return true;
}
// Create an event tap that listens for all mouse events
static const CGEventMask allMouseEventsMask = CGEventMaskBit(kCGEventLeftMouseDown) |
CGEventMaskBit(kCGEventLeftMouseDragged) |
CGEventMaskBit(kCGEventLeftMouseUp) |
CGEventMaskBit(kCGEventRightMouseDown) |
CGEventMaskBit(kCGEventRightMouseDragged) |
CGEventMaskBit(kCGEventRightMouseUp) |
CGEventMaskBit(kCGEventOtherMouseDown) |
CGEventMaskBit(kCGEventOtherMouseUp) |
CGEventMaskBit(kCGEventMouseMoved);
m_disabledSystemCursorEventTap = CGEventTapCreate(kCGSessionEventTap,
kCGHeadInsertEventTap,
kCGEventTapOptionDefault,
allMouseEventsMask,
&DisabledSysetmCursorEventTapCallback,
this);
if (m_disabledSystemCursorEventTap == nullptr)
{
return false;
}
// Create a run loop source
m_disabledSystemCursorRunLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, m_disabledSystemCursorEventTap, 0);
if (m_disabledSystemCursorRunLoopSource == nullptr)
{
CFRelease(m_disabledSystemCursorEventTap);
m_disabledSystemCursorEventTap = nullptr;
return false;
}
// Add the run loop source and enable the event tap
CFRunLoopAddSource(CFRunLoopGetCurrent(), m_disabledSystemCursorRunLoopSource, kCFRunLoopCommonModes);
CGEventTapEnable(m_disabledSystemCursorEventTap, true);
// Initialize the constrained system cursor position
CGEventRef event = CGEventCreate(nil);
m_disabledSystemCursorPosition = CGEventGetLocation(event);
CFRelease(event);
return true;
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceMouseMac::DestroyDisabledSystemCursorEventTap()
{
if (m_disabledSystemCursorEventTap == nullptr)
{
// It has already been destroyed
return;
}
// Disable the event tap and remove the run loop source
CGEventTapEnable(m_disabledSystemCursorEventTap, false);
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), m_disabledSystemCursorRunLoopSource, kCFRunLoopCommonModes);
// Destroy the run loop source
CFRelease(m_disabledSystemCursorRunLoopSource);
m_disabledSystemCursorRunLoopSource = nullptr;
// Destroy the event tap
CFRelease(m_disabledSystemCursorEventTap);
m_disabledSystemCursorEventTap = nullptr;
}
} // 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,51 @@
/*
* 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/Utils/Utils.h>
#include <unistd.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 mac, if the neighborhood name was not provided we
//! will use the hostname 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[512];
if (gethostname(localhost, sizeof(localhost)) == 0)
{
neighborhoodName = localhost;
}
return neighborhoodName;
}
}
} // namespace AzFramework
@@ -0,0 +1,135 @@
/*
* 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/Windowing/NativeWindow.h>
#include <AppKit/AppKit.h>
@class NSWindow;
namespace AzFramework
{
class NativeWindowImpl_Darwin final
: public NativeWindow::Implementation
{
public:
AZ_CLASS_ALLOCATOR(NativeWindowImpl_Darwin, AZ::SystemAllocator, 0);
NativeWindowImpl_Darwin() = default;
~NativeWindowImpl_Darwin() override;
// NativeWindow::Implementation overrides...
void InitWindow(const AZStd::string& title,
const WindowGeometry& geometry,
const WindowStyleMasks& styleMasks) 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 NSWindowStyleMask ConvertToNSWindowStyleMask(const WindowStyleMasks& styleMasks);
NSWindow* m_nativeWindow;
NSString* m_windowTitle;
};
NativeWindow::Implementation* NativeWindow::Implementation::Create()
{
return aznew NativeWindowImpl_Darwin();
}
NativeWindowImpl_Darwin::~NativeWindowImpl_Darwin()
{
[m_nativeWindow release];
m_nativeWindow = nil;
}
void NativeWindowImpl_Darwin::InitWindow(const AZStd::string& title,
const WindowGeometry& geometry,
const WindowStyleMasks& styleMasks)
{
AZ_UNUSED(windowType);
m_width = geometry.m_width;
m_height = geometry.m_height;
SetWindowTitle(title);
CGRect screenBounds = CGRectMake(geometry.m_posX, geometry.m_posY, geometry.m_width, geometry.m_height);
// Create the window
NSUInteger styleMask = ConvertToNSWindowStyleMask(styleMasks);
m_nativeWindow = [[NSWindow alloc] initWithContentRect: screenBounds styleMask: styleMask backing: NSBackingStoreBuffered defer:false];
// Add a fullscreen button in the upper right of the title bar.
[m_nativeWindow setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary];
// Make the window active
[m_nativeWindow makeKeyAndOrderFront:nil];
m_nativeWindow.title = m_windowTitle;
}
NativeWindowHandle NativeWindowImpl_Darwin::GetWindowHandle() const
{
return m_nativeWindow;
}
void NativeWindowImpl_Darwin::SetWindowTitle(const AZStd::string& title)
{
m_windowTitle = [NSString stringWithCString:title.c_str() encoding:NSUTF8StringEncoding];
m_nativeWindow.title = m_windowTitle;
}
void NativeWindowImpl_Darwin::ResizeClientArea( WindowSize clientAreaSize )
{
NSRect contentRect = [m_nativeWindow contentLayoutRect];
if (clientAreaSize.m_width != NSWidth(contentRect) || clientAreaSize.m_height != NSHeight(contentRect))
{
NSRect newContentRect = NSMakeRect(NSMinX(contentRect), NSMinY(contentRect), clientAreaSize.m_width, clientAreaSize.m_height);
NSRect newFrameRect = [m_nativeWindow frameRectForContentRect:newContentRect];
//This will also activate windowDidResize callback which in turn will call OnWindowResized event so no need to call it directly here
[m_nativeWindow setFrame:newFrameRect display:YES animate:NO];
m_width = clientAreaSize.m_width;
m_height = clientAreaSize.m_height;
}
}
bool NativeWindowImpl_Darwin::GetFullScreenState() const
{
return ([m_nativeWindow styleMask] & NSWindowStyleMaskFullScreen) == NSWindowStyleMaskFullScreen;
}
void NativeWindowImpl_Darwin::SetFullScreenState(bool fullScreenState)
{
if (GetFullScreenState() != fullScreenState)
{
[m_nativeWindow toggleFullScreen:nil];
}
}
NSWindowStyleMask NativeWindowImpl_Darwin::ConvertToNSWindowStyleMask(const WindowStyleMasks& styleMasks)
{
NSWindowStyleMask nativeMask = styleMasks.m_platformSpecificStyleMask;
const uint32_t mask = styleMasks.m_platformAgnosticStyleMask;
if (mask & WindowStyleMasks::WINDOW_STYLE_RESIZEABLE) { nativeMask |= NSWindowStyleMaskResizable; }
if (mask & WindowStyleMasks::WINDOW_STYLE_TITLED) { nativeMask |= NSWindowStyleMaskTitled; }
if (mask & WindowStyleMasks::WINDOW_STYLE_CLOSABLE) { nativeMask |= NSWindowStyleMaskClosable; }
if (mask & WindowStyleMasks::WINDOW_STYLE_MINIMIZE) { nativeMask |= NSWindowStyleMaskMiniaturizable; }
const NSWindowStyleMask defaultMask = NSWindowStyleMaskResizable | NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable;
return nativeMask ? nativeMask : defaultMask;
}
} // namespace AzFramework
@@ -0,0 +1,25 @@
#
# 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.
#
find_library(APPKIT_LIBRARY AppKit)
find_library(GAME_CONTROLLER_LIBRARY GameController)
find_library(CARBON_LIBRARY Carbon)
find_library(CORE_SERVICES_LIBRARY CoreServices)
find_library(CORE_GRAPHICS_LIBRARY CoreGraphics)
set(LY_BUILD_DEPENDENCIES
PRIVATE
${APPKIT_LIBRARY}
${GAME_CONTROLLER_LIBRARY}
${CARBON_LIBRARY}
${CORE_SERVICES_LIBRARY}
${CORE_GRAPHICS_LIBRARY}
)
@@ -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.
#
set(FILES
AzFramework/AzFramework_Traits_Platform.h
AzFramework/AzFramework_Traits_Mac.h
AzFramework/API/ApplicationAPI_Platform.h
AzFramework/API/ApplicationAPI_Mac.h
AzFramework/Application/Application_Mac.mm
AzFramework/Asset/AssetSystemComponentHelper_Mac.cpp
../Common/UnixLike/AzFramework/IO/LocalFileIO_UnixLike.cpp
../Common/Default/AzFramework/Network/AssetProcessorConnection_Default.cpp
../Common/Unimplemented/AzFramework/StreamingInstall/StreamingInstall_Unimplemented.cpp
AzFramework/TargetManagement/TargetManagementComponent_Mac.cpp
AzFramework/Windowing/NativeWindow_Mac.mm
AzFramework/Input/Buses/Notifications/RawInputNotificationBus_Platform.h
AzFramework/Input/Buses/Notifications/RawInputNotificationBus_Mac.h
../Common/Apple/AzFramework/Input/Devices/Gamepad/InputDeviceGamepad_Apple.mm
AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard_Mac.mm
../Common/Unimplemented/AzFramework/Input/Devices/Motion/InputDeviceMotion_Unimplemented.cpp
AzFramework/Input/Devices/Mouse/InputDeviceMouse_Mac.mm
../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_Mac.h
)
@@ -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
)
@@ -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_iOS.h>
@@ -0,0 +1,41 @@
/*
* 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 IosLifecycleEvents
: public AZ::EBusTraits
{
public:
// Bus Configuration
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
virtual ~IosLifecycleEvents() {}
using Bus = AZ::EBus<IosLifecycleEvents>;
virtual void OnWillResignActive() {} // Constrain
virtual void OnDidBecomeActive() {} // Unconstrain
virtual void OnDidEnterBackground() {} // Suspend
virtual void OnWillEnterForeground() {} // Resume
virtual void OnWillTerminate() {} // Terminate
virtual void OnDidReceiveMemoryWarning() {} // Low memory
};
} // namespace AzFramework
@@ -0,0 +1,125 @@
/*
* 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/API/ApplicationAPI_Platform.h>
#include <AzFramework/Application/Application.h>
#include <UIKit/UIKit.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace AzFramework
{
////////////////////////////////////////////////////////////////////////////////////////////////
class ApplicationIos
: public Application::Implementation
, public IosLifecycleEvents::Bus::Handler
{
public:
////////////////////////////////////////////////////////////////////////////////////////////
AZ_CLASS_ALLOCATOR(ApplicationIos, AZ::SystemAllocator, 0);
ApplicationIos();
~ApplicationIos() override;
////////////////////////////////////////////////////////////////////////////////////////////
// IosLifecycleEvents
void OnWillResignActive() override;
void OnDidBecomeActive() override;
void OnDidEnterBackground() override;
void OnWillEnterForeground() override;
void OnWillTerminate() override;
void OnDidReceiveMemoryWarning() override;
////////////////////////////////////////////////////////////////////////////////////////////
// Application::Implementation
void PumpSystemEventLoopOnce() override;
void PumpSystemEventLoopUntilEmpty() override;
private:
ApplicationLifecycleEvents::Event m_lastEvent;
};
////////////////////////////////////////////////////////////////////////////////////////////////
Application::Implementation* Application::Implementation::Create()
{
return aznew ApplicationIos();
}
////////////////////////////////////////////////////////////////////////////////////////////////
ApplicationIos::ApplicationIos()
: m_lastEvent(ApplicationLifecycleEvents::Event::None)
{
IosLifecycleEvents::Bus::Handler::BusConnect();
}
////////////////////////////////////////////////////////////////////////////////////////////////
ApplicationIos::~ApplicationIos()
{
IosLifecycleEvents::Bus::Handler::BusDisconnect();
}
////////////////////////////////////////////////////////////////////////////////////////////////
void ApplicationIos::OnWillResignActive()
{
EBUS_EVENT(ApplicationLifecycleEvents::Bus, OnApplicationConstrained, m_lastEvent);
m_lastEvent = ApplicationLifecycleEvents::Event::Constrain;
}
////////////////////////////////////////////////////////////////////////////////////////////////
void ApplicationIos::OnDidBecomeActive()
{
EBUS_EVENT(ApplicationLifecycleEvents::Bus, OnApplicationUnconstrained, m_lastEvent);
m_lastEvent = ApplicationLifecycleEvents::Event::Unconstrain;
}
////////////////////////////////////////////////////////////////////////////////////////////////
void ApplicationIos::OnDidEnterBackground()
{
EBUS_EVENT(ApplicationLifecycleEvents::Bus, OnApplicationSuspended, m_lastEvent);
m_lastEvent = ApplicationLifecycleEvents::Event::Suspend;
}
////////////////////////////////////////////////////////////////////////////////////////////////
void ApplicationIos::OnWillEnterForeground()
{
EBUS_EVENT(ApplicationLifecycleEvents::Bus, OnApplicationResumed, m_lastEvent);
m_lastEvent = ApplicationLifecycleEvents::Event::Resume;
}
////////////////////////////////////////////////////////////////////////////////////////////////
void ApplicationIos::OnWillTerminate()
{
EBUS_EVENT(ApplicationLifecycleEvents::Bus, OnMobileApplicationWillTerminate);
}
////////////////////////////////////////////////////////////////////////////////////////////////
void ApplicationIos::OnDidReceiveMemoryWarning()
{
EBUS_EVENT(ApplicationLifecycleEvents::Bus, OnMobileApplicationLowMemoryWarning);
}
////////////////////////////////////////////////////////////////////////////////////////////////
void ApplicationIos::PumpSystemEventLoopOnce()
{
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.0, TRUE);
}
////////////////////////////////////////////////////////////////////////////////////////////////
void ApplicationIos::PumpSystemEventLoopUntilEmpty()
{
SInt32 result;
do
{
result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, DBL_EPSILON, TRUE);
}
while (result == kCFRunLoopRunHandledSource);
}
} // 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_iOS.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,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_iOS.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 (true)
#define AZ_TRAIT_AZFRAMEWORK_BOOTSTRAP_CFG_CURRENT_PLATFORM "ios"
#define AZ_TRAIT_AZFRAMEWORK_USE_C11_LOCALTIME_S 0
#define AZ_TRAIT_AZFRAMEWORK_USE_ERRNO_T_TYPEDEF 0
#define AZ_TRAIT_AZFRAMEWORK_USE_POSIX_LOCALTIME_R 1
@@ -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_iOS.h>
@@ -0,0 +1,64 @@
/*
* 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>
@class UITouch;
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace AzFramework
{
////////////////////////////////////////////////////////////////////////////////////////////////
//! EBus interface used to listen for raw ios input events broadcast by the system. Applications
//! that want raw ios events to be processed by the AzFramework input system must broadcast all
//! input events received by a UIResponder instance, which is the lowest level we can get input.
//!
//! Input events should only be broadcast by a single UIResponder object, ideally from a custom
//! UIApplication object (which is guaranteed to be the last object in the ios responder chain).
//! This ensures that any Cocoa/UIKit controls that might be active are allowed the opportunity
//! to process input events before the engine (even though this scenario is unlikely for a game).
//!
//! It's possible to receive multiple touch events per index (finger) per frame, and while it is
//! possible to pump the ios event loop from any thread it is only possible to process raw touch
//! events received by a UIResponder instance, which is guaranteed to happen on the main thread.
//!
//! This EBus is intended primarily for the AzFramework input system to process ios input 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 raw ios events.
class RawInputNotificationsIos : 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 ~RawInputNotificationsIos() = default;
////////////////////////////////////////////////////////////////////////////////////////////
///@{
//! Process raw touch events (that will always be dispatched on the main thread)
//! \param[in] uiTouch The raw touch data
virtual void OnRawTouchEventBegan(const UITouch* /*uiTouch*/) {}
virtual void OnRawTouchEventMoved(const UITouch* /*uiTouch*/) {}
virtual void OnRawTouchEventEnded(const UITouch* /*uiTouch*/) {}
///@}
};
using RawInputNotificationBusIos = AZ::EBus<RawInputNotificationsIos>;
} // namespace AzFramework
@@ -0,0 +1,436 @@
/*
* 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/Devices/Motion/InputDeviceMotion.h>
#include <CoreMotion/CoreMotion.h>
#include <UIKit/UIKit.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace AzFramework
{
////////////////////////////////////////////////////////////////////////////////////////////////
//! Platform specific implementation for ios motion input devices
class InputDeviceMotionIos : public InputDeviceMotion::Implementation
{
public:
////////////////////////////////////////////////////////////////////////////////////////////
// Allocator
AZ_CLASS_ALLOCATOR(InputDeviceMotionIos, AZ::SystemAllocator, 0);
////////////////////////////////////////////////////////////////////////////////////////////
//! Constructor
//! \param[in] inputDevice Reference to the input device being implemented
InputDeviceMotionIos(InputDeviceMotion& inputDevice);
////////////////////////////////////////////////////////////////////////////////////////////
//! Destructor
~InputDeviceMotionIos() override;
private:
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceMotion::Implementation::IsConnected
bool IsConnected() const override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceMotion::Implementation::TickInputDevice
void TickInputDevice() override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceMotion::Implementation::RefreshMotionSensors
void RefreshMotionSensors(const InputDeviceRequests::InputChannelIdSet& enabledChannelIds) override;
////////////////////////////////////////////////////////////////////////////////////////////
//! Refresh the active state of the accelerometer based on the channels that are enabled
//! \param[in] enabledChannelIds Set of motion input channel ids that should be enabled
void RefreshAccelerometer(const InputDeviceRequests::InputChannelIdSet& enabledChannelIds);
////////////////////////////////////////////////////////////////////////////////////////////
//! Refresh the active state of the gyroscope based on the channels that are enabled
//! \param[in] enabledChannelIds Set of motion input channel ids that should be enabled
void RefreshGyroscope(const InputDeviceRequests::InputChannelIdSet& enabledChannelIds);
////////////////////////////////////////////////////////////////////////////////////////////
//! Refresh the active state of the magnetometer based on the channels that are enabled
//! \param[in] enabledChannelIds Set of motion input channel ids that should be enabled
void RefreshMagnetometer(const InputDeviceRequests::InputChannelIdSet& enabledChannelIds);
////////////////////////////////////////////////////////////////////////////////////////////
//! Refresh the active state of device motion based on the channels that are enabled
//! \param[in] enabledChannelIds Set of motion input channel ids that should be enabled
void RefreshDeviceMotion(const InputDeviceRequests::InputChannelIdSet& enabledChannelIds);
////////////////////////////////////////////////////////////////////////////////////////////
//! Given a vector relative to a device held upright in portrait orientation as shown below:
//!
//! _________ +y -z
//! | | | /
//! | | | /
//! | Device | |/ - Right-handed, y-up coordinate system
//! | Screen | -x______|______+x - The +y axis points out of the top of the device
//! | | /| - The +z axis points out of the front of the screen
//! | | / |
//! |_________| / |
//! |____O____| +z -y
//!
//! return another vector relative to the specified display orientation, and such that the
//! +y axis points out the back of the screen and z+ axis points out the top of the device.
//! This flipping of axes is to match Lumberyard's z-up and left-handed coordinate system.
//!
//! \param[in] x The x component of the vector to be aligned
//! \param[in] y The y component of the vector to be aligned
//! \param[in] z The z component of the vector to be aligned
//! \param[in] displayOrientation The orientation to make the vector relative to
//! \return A vector made relative to the specified display orientation
AZ::Vector3 MakeVectorRelativeToOrientation(float x, float y, float z,
UIInterfaceOrientation displayOrientation);
////////////////////////////////////////////////////////////////////////////////////////////
//! Given a quaternion relative to a device held upright in portrait orientation,
//! return another quaternion relative to the specified display orientation.
//!
//! \param[in] x The x component of the quaternion to be aligned
//! \param[in] y The y component of the quaternion to be aligned
//! \param[in] z The z component of the quaternion to be aligned
//! \param[in] displayOrientation The orientation to make the vector relative to
//! \return A quaternion made relative to the specified display orientation
AZ::Quaternion MakeQuaternionRelativeToOrientation(float w, float x, float y, float z,
UIInterfaceOrientation displayOrientation);
////////////////////////////////////////////////////////////////////////////////////////////
//! Variables
CMMotionManager* m_motionManager; //!< Reference to the motion manager
};
////////////////////////////////////////////////////////////////////////////////////////////////
InputDeviceMotion::Implementation* InputDeviceMotion::Implementation::Create(
InputDeviceMotion& inputDevice)
{
return aznew InputDeviceMotionIos(inputDevice);
}
////////////////////////////////////////////////////////////////////////////////////////////////
InputDeviceMotionIos::InputDeviceMotionIos(InputDeviceMotion& inputDevice)
: InputDeviceMotion::Implementation(inputDevice)
{
m_motionManager = [[CMMotionManager alloc] init];
}
////////////////////////////////////////////////////////////////////////////////////////////////
InputDeviceMotionIos::~InputDeviceMotionIos()
{
[m_motionManager release];
}
////////////////////////////////////////////////////////////////////////////////////////////////
bool InputDeviceMotionIos::IsConnected() const
{
// Motion input is always available on ios
return true;
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceMotionIos::TickInputDevice()
{
UIInterfaceOrientation uiOrientation = UIInterfaceOrientationUnknown;
#if defined(__IPHONE_13_0) || defined(__TVOS_13_0)
if(@available(iOS 13.0, tvOS 13.0, *))
{
UIWindow* foundWindow = nil;
NSArray* windows = [[UIApplication sharedApplication] windows];
for (UIWindow* window in windows)
{
if (window.isKeyWindow)
{
foundWindow = window;
break;
}
}
UIWindowScene* windowScene = foundWindow ? foundWindow.windowScene : nullptr;
AZ_Assert(windowScene, "WindowScene is invalid");
if(windowScene)
{
uiOrientation = windowScene.interfaceOrientation;
}
}
#else
uiOrientation = UIApplication.sharedApplication.statusBarOrientation;
#endif
if (CMAccelerometerData* accelerometerData = m_motionManager.accelerometerData)
{
// Process raw acceleration
const AZ::Vector3 accelerationRaw = MakeVectorRelativeToOrientation(accelerometerData.acceleration.x,
accelerometerData.acceleration.y,
accelerometerData.acceleration.z,
uiOrientation);
ProcessAccelerationData(InputDeviceMotion::Acceleration::Raw, accelerationRaw);
}
if (CMGyroData* gyroData = m_motionManager.gyroData)
{
// Process raw rotation rate
const AZ::Vector3 rotationRateRaw = MakeVectorRelativeToOrientation(gyroData.rotationRate.x,
gyroData.rotationRate.y,
gyroData.rotationRate.z,
uiOrientation);
ProcessRotationRateData(InputDeviceMotion::RotationRate::Raw, rotationRateRaw);
}
if (CMMagnetometerData* magnetometerData = m_motionManager.magnetometerData)
{
// Process raw magnetic field
const AZ::Vector3 magneticFieldRaw = MakeVectorRelativeToOrientation(magnetometerData.magneticField.x,
magnetometerData.magneticField.y,
magnetometerData.magneticField.z,
uiOrientation);
ProcessMagneticFieldData(InputDeviceMotion::MagneticField::Raw, magneticFieldRaw);
}
if (CMDeviceMotion* deviceMotion = m_motionManager.deviceMotion)
{
// Process user acceleration
const AZ::Vector3 accelerationUser = MakeVectorRelativeToOrientation(deviceMotion.userAcceleration.x,
deviceMotion.userAcceleration.y,
deviceMotion.userAcceleration.z,
uiOrientation);
ProcessAccelerationData(InputDeviceMotion::Acceleration::User, accelerationUser);
// Process gravity acceleration
const AZ::Vector3 accelerationGravity = MakeVectorRelativeToOrientation(deviceMotion.gravity.x,
deviceMotion.gravity.y,
deviceMotion.gravity.z,
uiOrientation);
ProcessAccelerationData(InputDeviceMotion::Acceleration::Gravity, accelerationGravity);
// Process unbiased rotation rate
const AZ::Vector3 rotationRateUnbiased = MakeVectorRelativeToOrientation(deviceMotion.rotationRate.x,
deviceMotion.rotationRate.y,
deviceMotion.rotationRate.z,
uiOrientation);
ProcessRotationRateData(InputDeviceMotion::RotationRate::Unbiased, rotationRateUnbiased);
if (deviceMotion.magneticField.accuracy != CMMagneticFieldCalibrationAccuracyUncalibrated)
{
// Process unbiased magnetic field
const AZ::Vector3 magneticFieldUnbiased = MakeVectorRelativeToOrientation(deviceMotion.magneticField.field.x,
deviceMotion.magneticField.field.y,
deviceMotion.magneticField.field.z,
uiOrientation);
ProcessMagneticFieldData(InputDeviceMotion::MagneticField::Unbiased, magneticFieldUnbiased);
// Calculate and process magnetic north
const AZ::Vector3 gravityNormalized = accelerationGravity.GetNormalized();
const AZ::Vector3 magneticFieldNormalized = magneticFieldUnbiased.GetNormalized();
const AZ::Vector3 magneticEastNormalized = gravityNormalized.Cross(magneticFieldNormalized).GetNormalized();
const AZ::Vector3 magneticNorthNormalized = magneticEastNormalized.Cross(gravityNormalized).GetNormalized();
ProcessMagneticFieldData(InputDeviceMotion::MagneticField::North, magneticNorthNormalized);
}
// Process current orientation
const AZ::Quaternion orientation = MakeQuaternionRelativeToOrientation(deviceMotion.attitude.quaternion.w,
deviceMotion.attitude.quaternion.x,
deviceMotion.attitude.quaternion.y,
deviceMotion.attitude.quaternion.z,
uiOrientation);
ProcessOrientationData(InputDeviceMotion::Orientation::Current, orientation);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceMotionIos::RefreshMotionSensors(
const InputDeviceRequests::InputChannelIdSet& enabledChannelIds)
{
RefreshAccelerometer(enabledChannelIds);
RefreshGyroscope(enabledChannelIds);
RefreshMagnetometer(enabledChannelIds);
RefreshDeviceMotion(enabledChannelIds);
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceMotionIos::RefreshAccelerometer(
const InputDeviceRequests::InputChannelIdSet& enabledChannelIds)
{
const bool shouldBeActive = enabledChannelIds.find(InputDeviceMotion::Acceleration::Raw) != enabledChannelIds.end();
if (shouldBeActive == m_motionManager.accelerometerActive)
{
return;
}
if (!shouldBeActive)
{
[m_motionManager stopAccelerometerUpdates];
return;
}
if (!m_motionManager.accelerometerAvailable)
{
return;
}
[m_motionManager startAccelerometerUpdates];
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceMotionIos::RefreshGyroscope(
const InputDeviceRequests::InputChannelIdSet& enabledChannelIds)
{
const bool shouldBeActive = enabledChannelIds.find(InputDeviceMotion::RotationRate::Raw) != enabledChannelIds.end();
if (shouldBeActive == m_motionManager.gyroActive)
{
return;
}
if (!shouldBeActive)
{
[m_motionManager stopGyroUpdates];
return;
}
if (!m_motionManager.gyroAvailable)
{
return;
}
[m_motionManager startGyroUpdates];
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceMotionIos::RefreshMagnetometer(
const InputDeviceRequests::InputChannelIdSet& enabledChannelIds)
{
const bool shouldBeActive = enabledChannelIds.find(InputDeviceMotion::MagneticField::Raw) != enabledChannelIds.end();
if (shouldBeActive == m_motionManager.magnetometerActive)
{
return;
}
if (!shouldBeActive)
{
[m_motionManager stopMagnetometerUpdates];
return;
}
if (!m_motionManager.magnetometerAvailable)
{
return;
}
[m_motionManager startMagnetometerUpdates];
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceMotionIos::RefreshDeviceMotion(
const InputDeviceRequests::InputChannelIdSet& enabledChannelIds)
{
const bool shouldBeActive = enabledChannelIds.find(InputDeviceMotion::Acceleration::User) != enabledChannelIds.end() ||
enabledChannelIds.find(InputDeviceMotion::Acceleration::Gravity) != enabledChannelIds.end() ||
enabledChannelIds.find(InputDeviceMotion::RotationRate::Unbiased) != enabledChannelIds.end() ||
enabledChannelIds.find(InputDeviceMotion::MagneticField::Unbiased) != enabledChannelIds.end() ||
enabledChannelIds.find(InputDeviceMotion::MagneticField::North) != enabledChannelIds.end() ||
enabledChannelIds.find(InputDeviceMotion::Orientation::Current) != enabledChannelIds.end();
const bool calibratedMagnetometerRequired = enabledChannelIds.find(InputDeviceMotion::MagneticField::Unbiased) != enabledChannelIds.end() ||
enabledChannelIds.find(InputDeviceMotion::MagneticField::North) != enabledChannelIds.end();
if (shouldBeActive == m_motionManager.deviceMotionActive &&
calibratedMagnetometerRequired == m_motionManager.showsDeviceMovementDisplay)
{
return;
}
// At this point device motion is either:
// - Not active and we want it active, in which case calling this will do nothing
// - Active and we want it inactive, in which case this will do so and we'll return below
// - Active and we still want it active, but we now need to calibrate the magnetometer by
// showing the device movement display, which won't unless we stop motion device updates.
[m_motionManager stopDeviceMotionUpdates];
if (!shouldBeActive)
{
return;
}
if (!m_motionManager.deviceMotionAvailable)
{
return;
}
m_motionManager.showsDeviceMovementDisplay = calibratedMagnetometerRequired;
// For reasons unknown, calibrated magnetic field values are only returned
// if 'showsDeviceMovementDisplay == true' and device motion updates are
// started using a reference frame that actually uses the magnetometer.
const CMAttitudeReferenceFrame referenceFrame = calibratedMagnetometerRequired ?
CMAttitudeReferenceFrameXArbitraryCorrectedZVertical :
CMAttitudeReferenceFrameXArbitraryZVertical; // Uses less battery
[m_motionManager startDeviceMotionUpdatesUsingReferenceFrame : referenceFrame];
}
////////////////////////////////////////////////////////////////////////////////////////////////
AZ::Vector3 InputDeviceMotionIos::MakeVectorRelativeToOrientation(
float x, float y, float z, UIInterfaceOrientation displayOrientation)
{
switch(displayOrientation)
{
case UIInterfaceOrientationLandscapeLeft:
{
return AZ::Vector3(y, -z, -x);
}
break;
case UIInterfaceOrientationLandscapeRight:
{
return AZ::Vector3(-y, -z, x);
}
break;
case UIInterfaceOrientationPortraitUpsideDown:
{
return AZ::Vector3(-x, -z, -y);
}
break;
case UIInterfaceOrientationPortrait:
case UIInterfaceOrientationUnknown:
default:
{
return AZ::Vector3(x, -z, y);
}
break;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
AZ::Quaternion InputDeviceMotionIos::MakeQuaternionRelativeToOrientation(
float w, float x, float y, float z, UIInterfaceOrientation displayOrientation)
{
AZ::Quaternion quaternion = AZ::Quaternion::CreateFromVector3AndValue(AZ::Vector3(x, y, z), w);
switch(displayOrientation)
{
case UIInterfaceOrientationLandscapeLeft:
{
quaternion *= AZ::Quaternion::CreateRotationZ(AZ::Constants::HalfPi);
}
break;
case UIInterfaceOrientationLandscapeRight:
{
quaternion *= AZ::Quaternion::CreateRotationZ(-AZ::Constants::HalfPi);
}
break;
case UIInterfaceOrientationPortraitUpsideDown:
{
quaternion *= AZ::Quaternion::CreateRotationZ(AZ::Constants::Pi);
}
break;
case UIInterfaceOrientationPortrait:
case UIInterfaceOrientationUnknown:
default: break;
}
return quaternion;
}
} // namespace AzFramework
@@ -0,0 +1,201 @@
/*
* 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/Devices/Touch/InputDeviceTouch.h>
#include <AzFramework/Input/Buses/Notifications/RawInputNotificationBus_Platform.h>
#include <UIKit/UIKit.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace AzFramework
{
////////////////////////////////////////////////////////////////////////////////////////////////
//! Platform specific implementation for ios touch input devices
class InputDeviceTouchIos : public InputDeviceTouch::Implementation
, public RawInputNotificationBusIos::Handler
{
public:
////////////////////////////////////////////////////////////////////////////////////////////
// Allocator
AZ_CLASS_ALLOCATOR(InputDeviceTouchIos, AZ::SystemAllocator, 0);
////////////////////////////////////////////////////////////////////////////////////////////
//! Constructor
//! \param[in] inputDevice Reference to the input device being implemented
InputDeviceTouchIos(InputDeviceTouch& inputDevice);
////////////////////////////////////////////////////////////////////////////////////////////
//! Destructor
~InputDeviceTouchIos() override;
private:
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceTouch::Implementation::IsConnected
bool IsConnected() const override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceTouch::Implementation::TickInputDevice
void TickInputDevice() override;
////////////////////////////////////////////////////////////////////////////////////////////
///@{
//! Process raw touch events (that will always be dispatched on the main thread)
//! \param[in] uiTouch The raw touch data
void OnRawTouchEventBegan(const UITouch* uiTouch) override;
void OnRawTouchEventMoved(const UITouch* uiTouch) override;
void OnRawTouchEventEnded(const UITouch* uiTouch) override;
///@}
////////////////////////////////////////////////////////////////////////////////////////////
//! Convenience function to initialize a raw touch event
static RawTouchEvent InitRawTouchEvent(const UITouch* touch, uint32_t index);
////////////////////////////////////////////////////////////////////////////////////////////
//! ios does not provide us with the index of a touch, but it does persist UITouch objects
//! throughout a multi-touch sequence, so we can keep track of the touch indices ourselves.
AZStd::vector<const UITouch*> m_activeTouches;
};
////////////////////////////////////////////////////////////////////////////////////////////////
InputDeviceTouch::Implementation* InputDeviceTouch::Implementation::Create(InputDeviceTouch& inputDevice)
{
return aznew InputDeviceTouchIos(inputDevice);
}
////////////////////////////////////////////////////////////////////////////////////////////////
InputDeviceTouchIos::InputDeviceTouchIos(InputDeviceTouch& inputDevice)
: InputDeviceTouch::Implementation(inputDevice)
, m_activeTouches()
{
// The maximum number of active touches tracked by ios is actually device dependent, and at
// this time appears to be 5 for iPhone/iPodTouch and 11 for iPad. There is no API to query
// or set this, but ten seems more than sufficient for most applications, especially games.
m_activeTouches.resize(InputDeviceTouch::Touch::All.size());
RawInputNotificationBusIos::Handler::BusConnect();
}
////////////////////////////////////////////////////////////////////////////////////////////////
InputDeviceTouchIos::~InputDeviceTouchIos()
{
RawInputNotificationBusIos::Handler::BusDisconnect();
}
////////////////////////////////////////////////////////////////////////////////////////////////
bool InputDeviceTouchIos::IsConnected() const
{
// Touch input is always available on ios
return true;
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceTouchIos::TickInputDevice()
{
// The ios event loop has just been pumped in InputSystemComponentIos::PreTickInputDevices,
// so we now just need to process any raw events that have been queued since the last frame
ProcessRawEventQueues();
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceTouchIos::OnRawTouchEventBegan(const UITouch* uiTouch)
{
for (uint32_t i = 0; i < InputDeviceTouch::Touch::All.size(); ++i)
{
// Use the first available index.
if (m_activeTouches[i] == nullptr)
{
m_activeTouches[i] = uiTouch;
const RawTouchEvent rawTouchEvent = InitRawTouchEvent(uiTouch, i);
QueueRawTouchEvent(rawTouchEvent);
break;
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceTouchIos::OnRawTouchEventMoved(const UITouch* uiTouch)
{
for (uint32_t i = 0; i < InputDeviceTouch::Touch::All.size(); ++i)
{
if (m_activeTouches[i] == uiTouch)
{
const RawTouchEvent rawTouchEvent = InitRawTouchEvent(uiTouch, i);
QueueRawTouchEvent(rawTouchEvent);
break;
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputDeviceTouchIos::OnRawTouchEventEnded(const UITouch* uiTouch)
{
for (uint32_t i = 0; i < InputDeviceTouch::Touch::All.size(); ++i)
{
if (m_activeTouches[i] == uiTouch)
{
const RawTouchEvent rawTouchEvent = InitRawTouchEvent(uiTouch, i);
QueueRawTouchEvent(rawTouchEvent);
m_activeTouches[i] = nullptr;
break;
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
InputDeviceTouch::Implementation::RawTouchEvent InputDeviceTouchIos::InitRawTouchEvent(
const UITouch* touch,
uint32_t index)
{
CGPoint touchLocation = [touch locationInView: touch.view];
CGSize viewSize = [touch.view bounds].size;
const float normalizedLocationX = touchLocation.x / viewSize.width;
const float normalizedLocationY = touchLocation.y / viewSize.height;
const bool supportsForceTouch = UIScreen.mainScreen.traitCollection.forceTouchCapability == UIForceTouchCapabilityAvailable;
float pressure = supportsForceTouch ? touch.force / touch.maximumPossibleForce : 1.0f;
RawTouchEvent::State state = RawTouchEvent::State::Began;
switch (touch.phase)
{
case UITouchPhaseBegan:
{
state = RawTouchEvent::State::Began;
}
break;
case UITouchPhaseMoved:
case UITouchPhaseStationary: // Should never happen but if so treat it the same as moved
{
state = RawTouchEvent::State::Moved;
}
break;
case UITouchPhaseEnded:
case UITouchPhaseCancelled: // Should never happen but if so treat it the same as ended
{
state = RawTouchEvent::State::Ended;
pressure = 0.0f;
}
break;
}
return RawTouchEvent(normalizedLocationX,
normalizedLocationY,
pressure,
index,
state);
}
} // 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,71 @@
/*
* 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/Windowing/NativeWindow.h>
#include <UIKit/UIKit.h>
@class NSWindow;
namespace AzFramework
{
class NativeWindowImpl_Ios final
: public NativeWindow::Implementation
{
public:
AZ_CLASS_ALLOCATOR(NativeWindowImpl_Ios, AZ::SystemAllocator, 0);
NativeWindowImpl_Ios() = default;
~NativeWindowImpl_Ios() override;
// NativeWindow::Implementation overrides...
void InitWindow(const AZStd::string& title,
const WindowGeometry& geometry,
const WindowStyleMasks& styleMasks) override;
NativeWindowHandle GetWindowHandle() const override;
private:
UIWindow* m_nativeWindow;
};
NativeWindow::Implementation* NativeWindow::Implementation::Create()
{
return aznew NativeWindowImpl_Ios();
}
NativeWindowImpl_Ios::~NativeWindowImpl_Ios()
{
if (m_nativeWindow)
{
[m_nativeWindow release];
m_nativeWindow = nil;
}
}
void NativeWindowImpl_Ios::InitWindow([[maybe_unused]]const AZStd::string& title,
const WindowGeometry& geometry,
[[maybe_unused]]const WindowStyleMasks& styleMasks)
{
CGRect screenBounds = [[UIScreen mainScreen] bounds];
m_nativeWindow = [[UIWindow alloc] initWithFrame: screenBounds];
[m_nativeWindow makeKeyAndVisible];
m_width = geometry.m_width;
m_height = geometry.m_height;
}
NativeWindowHandle NativeWindowImpl_Ios::GetWindowHandle() const
{
return m_nativeWindow;
}
} // namespace AzFramework
@@ -0,0 +1,24 @@
#
# 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.
#
find_library(GAME_CONTROLLER_FRAMEWORK GameController)
find_library(UI_KIT_FRAMEWORK UIKit)
find_library(CORE_MOTION_FRAMEWORK CoreMotion)
find_library(CORE_GRAPHICS_FRAMEWORK CoreGraphics)
set(LY_BUILD_DEPENDENCIES
PRIVATE
${GAME_CONTROLLER_FRAMEWORK}
${UI_KIT_FRAMEWORK}
${CORE_MOTION_FRAMEWORK}
${CORE_GRAPHICS_FRAMEWORK}
)
@@ -0,0 +1,37 @@
#
# 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_iOS.h
AzFramework/API/ApplicationAPI_Platform.h
AzFramework/API/ApplicationAPI_iOS.h
AzFramework/Application/Application_iOS.mm
../Common/Unimplemented/AzFramework/Asset/AssetSystemComponentHelper_Unimplemented.cpp
../Common/UnixLike/AzFramework/IO/LocalFileIO_UnixLike.cpp
../Common/Default/AzFramework/Network/AssetProcessorConnection_Default.cpp
../Common/Unimplemented/AzFramework/StreamingInstall/StreamingInstall_Unimplemented.cpp
../Common/Default/AzFramework/TargetManagement/TargetManagementComponent_Default.cpp
AzFramework/Windowing/NativeWindow_ios.mm
AzFramework/Input/Buses/Notifications/RawInputNotificationBus_Platform.h
AzFramework/Input/Buses/Notifications/RawInputNotificationBus_iOS.h
../Common/Apple/AzFramework/Input/Devices/Gamepad/InputDeviceGamepad_Apple.mm
../Common/Unimplemented/AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard_Unimplemented.cpp
AzFramework/Input/Devices/Motion/InputDeviceMotion_iOS.mm
../Common/Unimplemented/AzFramework/Input/Devices/Mouse/InputDeviceMouse_Unimplemented.cpp
AzFramework/Input/Devices/Touch/InputDeviceTouch_iOS.mm
AzFramework/Input/User/LocalUserId_Platform.h
../Common/Default/AzFramework/Input/User/LocalUserId_Default.h
../Common/Apple/AzFramework/Input/Devices/VirtualKeyboard/InputDeviceVirtualKeyboard_Apple.mm
AzFramework/Archive/ArchiveVars_Platform.h
AzFramework/Archive/ArchiveVars_iOS.h
)