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,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
)