diff --git a/Assets/Editor/Prefabs/Default_Level.prefab b/Assets/Editor/Prefabs/Default_Level.prefab
index d02d669f53..0267131fa0 100644
--- a/Assets/Editor/Prefabs/Default_Level.prefab
+++ b/Assets/Editor/Prefabs/Default_Level.prefab
@@ -185,7 +185,7 @@
{
"id": {
"materialAssetId": {
- "guid": "{935F694A-8639-515B-8133-81CDC7948E5B}",
+ "guid": "{0CD745C0-6AA8-569A-A68A-73A3270986C4}",
"subId": 803645540
}
}
@@ -197,7 +197,7 @@
"id": {
"lodIndex": 0,
"materialAssetId": {
- "guid": "{935F694A-8639-515B-8133-81CDC7948E5B}",
+ "guid": "{0CD745C0-6AA8-569A-A68A-73A3270986C4}",
"subId": 803645540
}
}
diff --git a/Assets/Engine/Engine_Dependencies.xml b/Assets/Engine/Engine_Dependencies.xml
index 0d6541e18e..b969b3bc97 100644
--- a/Assets/Engine/Engine_Dependencies.xml
+++ b/Assets/Engine/Engine_Dependencies.xml
@@ -1,16 +1,9 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
+
+
+
+
+
+
+
+
diff --git a/Assets/Engine/SeedAssetList.seed b/Assets/Engine/SeedAssetList.seed
index aafbffbe8f..18ccd19dc3 100644
--- a/Assets/Engine/SeedAssetList.seed
+++ b/Assets/Engine/SeedAssetList.seed
@@ -1,621 +1,260 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
diff --git a/AutomatedTesting/Gem/PythonCoverage/gem.json b/AutomatedTesting/Gem/PythonCoverage/gem.json
index 39e327b5e3..b99ce0daad 100644
--- a/AutomatedTesting/Gem/PythonCoverage/gem.json
+++ b/AutomatedTesting/Gem/PythonCoverage/gem.json
@@ -2,6 +2,7 @@
"gem_name": "PythonCoverage",
"display_name": "PythonCoverage",
"license": "Apache-2.0 Or MIT",
+ "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt",
"origin": "Open 3D Engine - o3de.org",
"type": "Tool",
"summary": "A tool for generating gem coverage for Python tests.",
diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_processor_batch_dependency_tests.py b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_processor_batch_dependency_tests.py
index ee8e177bbb..303a012cda 100755
--- a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_processor_batch_dependency_tests.py
+++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_processor_batch_dependency_tests.py
@@ -47,82 +47,6 @@ class TestsAssetProcessorBatch_DependenycyTests(object):
"""
AssetProcessorBatch Dependency tests
"""
-
- @pytest.mark.test_case_id("C16877166")
- @pytest.mark.BAT
- @pytest.mark.assetpipeline
- # fmt:off
- def test_WindowsMacPlatforms_RunAPBatch_NotMissingDependency(self, ap_setup_fixture, asset_processor,
- workspace):
- # fmt:on
- """
- Engine Schema
- This test case has a conditional scenario depending on the existence of surfacetypes.xml in a project.
- Some projects have this file and others do not. Run the conditional scenario depending on the existence
- of the file in the project
- libs/materialeffects/surfacetypes.xml is listed as an entry engine_dependencies.xml
- libs/materialeffects/surfacetypes.xml is not listed as a missing dependency
- in the 'assetprocessorbatch' console output
-
- Test Steps:
- 1. Assets are pre-processed
- 2. Verify that engine_dependencies.xml exists
- 3. Verify engine_dependencies.xml has surfacetypes.xml present
- 4. Run Missing Dependency scanner against the engine_dependenciese.xml
- 5. Verify that Surfacetypes.xml is NOT in the missing depdencies output
- 6. Add the schema file which allows our xml parser to understand dependencies for our engine_dependencies file
- 7. Process assets
- 8. Run Missing Dependency scanner against the engine_dependenciese.xml
- 9. Verify that surfacetypes.xml is in the missing dependencies out
- """
-
- env = ap_setup_fixture
- BATCH_LOG_PATH = env["ap_batch_log_file"]
- asset_processor.create_temp_asset_root()
- asset_processor.add_relative_source_asset(os.path.join("Assets", "Engine", "engine_dependencies.xml"))
- asset_processor.add_scan_folder(os.path.join("Assets", "Engine"))
- asset_processor.add_relative_source_asset(os.path.join("Assets", "Engine", "Libs", "MaterialEffects", "surfacetypes.xml"))
-
- # Precondition: Assets are all processed
- asset_processor.batch_process()
-
- DEPENDENCIES_PATH = os.path.join(asset_processor.temp_project_cache(), "engine_dependencies.xml")
- assert os.path.exists(DEPENDENCIES_PATH), "The engine_dependencies.xml does not exist."
- surfacetypes_in_dependencies = False
- surfacetypes_missing_logline = False
-
- # Read engine_dependencies.xml to see if surfacetypes.xml is present
- with open(DEPENDENCIES_PATH, "r") as dependencies_file:
- for line in dependencies_file.readlines():
- if "surfacetypes.xml" in line:
- surfacetypes_in_dependencies = True
- logger.info("Surfacetypes.xml was listed in the engine_dependencies.xml file.")
- break
-
- if not surfacetypes_in_dependencies:
- logger.info("Surfacetypes.xml was not listed in the engine_dependencies.xml file.")
-
- _, output = asset_processor.batch_process(capture_output=True,
- extra_params="--dsp=%engine_dependencies.xml")
- log = APOutputParser(output)
- for _ in log.get_lines(run=-1, contains=["surfacetypes.xml", "Missing"]):
- surfacetypes_missing_logline = True
-
- assert surfacetypes_missing_logline, "Surfacetypes.xml not seen in the batch log as missing."
-
- # Add the schema file which allows our xml parser to understand dependencies for our engine_dependencies file
- asset_processor.add_relative_source_asset(os.path.join("Assets", "Engine", "Schema", "enginedependency.xmlschema"))
- asset_processor.batch_process()
-
- _, output = asset_processor.batch_process(capture_output=True,
- extra_params="--dsp=%engine_dependencies.xml")
- log = APOutputParser(output)
- surfacetypes_missing_logline = False
- for _ in log.get_lines(run=-1, contains=["surfacetypes.xml", "Missing"]):
- surfacetypes_missing_logline = True
-
- assert not surfacetypes_missing_logline, "Surfacetypes.xml not seen in the batch log as missing."
-
schemas = [
("C16877167", ".ent"),
("C16877168", "Environment.xml"),
diff --git a/AutomatedTesting/Gem/gem.json b/AutomatedTesting/Gem/gem.json
index 6c8c7829ce..df197df09d 100644
--- a/AutomatedTesting/Gem/gem.json
+++ b/AutomatedTesting/Gem/gem.json
@@ -2,10 +2,13 @@
"gem_name": "AutomatedTesting",
"display_name": "AutomatedTesting",
"license": "Apache-2.0 Or MIT",
+ "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt",
"origin": "Amazon Web Services, Inc.",
"type": "Code",
"summary": "Project Gem for customizing the AutomatedTesting project functionality.",
- "canonical_tags": ["Gem"],
+ "canonical_tags": [
+ "Gem"
+ ],
"user_tags": [],
"icon_path": "preview.png",
"requirements": ""
diff --git a/Code/Editor/CryEdit.cpp b/Code/Editor/CryEdit.cpp
index e4138da932..5cffedd774 100644
--- a/Code/Editor/CryEdit.cpp
+++ b/Code/Editor/CryEdit.cpp
@@ -45,6 +45,7 @@ AZ_POP_DISABLE_WARNING
// AzCore
#include
+#include
#include
#include
#include
@@ -1686,6 +1687,11 @@ bool CCryEditApp::InitInstance()
return false;
}
+ if (AZ::SettingsRegistryInterface* settingsRegistry = AZ::SettingsRegistry::Get())
+ {
+ AZ::ComponentApplicationLifecycle::SignalEvent(*settingsRegistry, "LegacySystemInterfaceCreated", R"({})");
+ }
+
// Process some queued events come from system init
// Such as asset catalog loaded notification.
// There are some systems need to load configurations from assets for post initialization but before loading level
diff --git a/Code/Editor/EditorViewportWidget.cpp b/Code/Editor/EditorViewportWidget.cpp
index b16758a07e..8e6983a9d7 100644
--- a/Code/Editor/EditorViewportWidget.cpp
+++ b/Code/Editor/EditorViewportWidget.cpp
@@ -43,10 +43,11 @@
// AzToolsFramework
#include
+#include
+#include
#include
#include
#include
-#include
// AtomToolsFramework
#include
@@ -1032,6 +1033,7 @@ void EditorViewportWidget::ConnectViewportInteractionRequestBus()
AzToolsFramework::ViewportInteraction::MainEditorViewportInteractionRequestBus::Handler::BusConnect(GetViewportId());
AzToolsFramework::ViewportInteraction::EditorEntityViewportInteractionRequestBus::Handler::BusConnect(GetViewportId());
m_viewportUi.ConnectViewportUiBus(GetViewportId());
+ AzFramework::ViewportBorderRequestBus::Handler::BusConnect(GetViewportId());
AzFramework::InputSystemCursorConstraintRequestBus::Handler::BusConnect();
}
@@ -1040,6 +1042,7 @@ void EditorViewportWidget::DisconnectViewportInteractionRequestBus()
{
AzFramework::InputSystemCursorConstraintRequestBus::Handler::BusDisconnect();
+ AzFramework::ViewportBorderRequestBus::Handler::BusDisconnect();
m_viewportUi.DisconnectViewportUiBus();
AzToolsFramework::ViewportInteraction::EditorEntityViewportInteractionRequestBus::Handler::BusDisconnect();
AzToolsFramework::ViewportInteraction::MainEditorViewportInteractionRequestBus::Handler::BusDisconnect();
@@ -2638,4 +2641,25 @@ void EditorViewportWidget::StopFullscreenPreview()
// Show the main window
MainWindow::instance()->show();
}
+
+AZStd::optional EditorViewportWidget::GetViewportBorderPadding() const
+{
+ if (auto viewportEditorModeTracker = AZ::Interface::Get())
+ {
+ auto viewportEditorModes = viewportEditorModeTracker->GetViewportEditorModes({ AzToolsFramework::GetEntityContextId() });
+ if (viewportEditorModes->IsModeActive(AzToolsFramework::ViewportEditorMode::Focus) ||
+ viewportEditorModes->IsModeActive(AzToolsFramework::ViewportEditorMode::Component))
+ {
+ AzFramework::ViewportBorderPadding viewportBorderPadding = {};
+ viewportBorderPadding.m_top = AzToolsFramework::ViewportUi::ViewportUiTopBorderSize;
+ viewportBorderPadding.m_left = AzToolsFramework::ViewportUi::ViewportUiLeftRightBottomBorderSize;
+ viewportBorderPadding.m_right = AzToolsFramework::ViewportUi::ViewportUiLeftRightBottomBorderSize;
+ viewportBorderPadding.m_bottom = AzToolsFramework::ViewportUi::ViewportUiLeftRightBottomBorderSize;
+ return viewportBorderPadding;
+ }
+ }
+
+ return AZStd::nullopt;
+}
+
#include
diff --git a/Code/Editor/EditorViewportWidget.h b/Code/Editor/EditorViewportWidget.h
index 68ea48c7f5..01f6068d56 100644
--- a/Code/Editor/EditorViewportWidget.h
+++ b/Code/Editor/EditorViewportWidget.h
@@ -38,6 +38,7 @@
#include
#include
+#include
// forward declarations.
class CBaseObject;
@@ -86,6 +87,7 @@ AZ_PUSH_DISABLE_DLL_EXPORT_BASECLASS_WARNING
AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING
class SANDBOX_API EditorViewportWidget final
: public QtViewport
+ , public AzFramework::ViewportBorderRequestBus::Handler
, private IEditorNotifyListener
, private IUndoManagerListener
, private Camera::EditorCameraRequestBus::Handler
@@ -120,6 +122,9 @@ public:
void SetFOV(float fov) override;
float GetFOV() const override;
+ // AzFramework::ViewportBorderRequestBus overrides ...
+ AZStd::optional GetViewportBorderPadding() const override;
+
private:
////////////////////////////////////////////////////////////////////////
// Private types ...
diff --git a/Code/Editor/Platform/Linux/Editor/Core/QtEditorApplication_linux.cpp b/Code/Editor/Platform/Linux/Editor/Core/QtEditorApplication_linux.cpp
index ad5e57479b..8ba152a9f9 100644
--- a/Code/Editor/Platform/Linux/Editor/Core/QtEditorApplication_linux.cpp
+++ b/Code/Editor/Platform/Linux/Editor/Core/QtEditorApplication_linux.cpp
@@ -10,6 +10,8 @@
#ifdef PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB
#include
+#include
+#include
#endif
namespace Editor
@@ -23,16 +25,34 @@ namespace Editor
return nullptr;
}
+ xcb_connection_t* EditorQtApplicationXcb::GetXcbConnectionFromQt()
+ {
+ QPlatformNativeInterface* native = platformNativeInterface();
+ AZ_Warning("EditorQtApplicationXcb", native, "Unable to retrieve the native platform interface");
+ if (!native)
+ {
+ return nullptr;
+ }
+ return reinterpret_cast(native->nativeResourceForIntegration(QByteArray("connection")));
+ }
+
+ void EditorQtApplicationXcb::OnStartPlayInEditor()
+ {
+ auto* interface = AzFramework::XcbConnectionManagerInterface::Get();
+ interface->SetEnableXInput(GetXcbConnectionFromQt(), true);
+ }
+
+ void EditorQtApplicationXcb::OnStopPlayInEditor()
+ {
+ auto* interface = AzFramework::XcbConnectionManagerInterface::Get();
+ interface->SetEnableXInput(GetXcbConnectionFromQt(), false);
+ }
+
bool EditorQtApplicationXcb::nativeEventFilter([[maybe_unused]] const QByteArray& eventType, void* message, long*)
{
if (GetIEditor()->IsInGameMode())
{
#ifdef PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB
- // We need to handle RAW Input events in a separate loop. This is a workaround to enable XInput2 RAW Inputs using Editor mode.
- // TODO To have this call here might be not be perfect.
- AzFramework::XcbEventHandlerBus::Broadcast(&AzFramework::XcbEventHandler::PollSpecialEvents);
-
- // Now handle the rest of the events.
AzFramework::XcbEventHandlerBus::Broadcast(
&AzFramework::XcbEventHandler::HandleXcbEvent, static_cast(message));
#endif
diff --git a/Code/Editor/Platform/Linux/Editor/Core/QtEditorApplication_linux.h b/Code/Editor/Platform/Linux/Editor/Core/QtEditorApplication_linux.h
index 8c145c3aa7..109ae1742b 100644
--- a/Code/Editor/Platform/Linux/Editor/Core/QtEditorApplication_linux.h
+++ b/Code/Editor/Platform/Linux/Editor/Core/QtEditorApplication_linux.h
@@ -6,19 +6,35 @@
*
*/
+#if !defined(Q_MOC_RUN)
#include
+#include
+#endif
+
+using xcb_connection_t = struct xcb_connection_t;
namespace Editor
{
- class EditorQtApplicationXcb : public EditorQtApplication
+ class EditorQtApplicationXcb
+ : public EditorQtApplication
+ , public AzToolsFramework::EditorEntityContextNotificationBus::Handler
{
Q_OBJECT
public:
EditorQtApplicationXcb(int& argc, char** argv)
: EditorQtApplication(argc, argv)
{
+ // Connect bus to listen for OnStart/StopPlayInEditor events
+ AzToolsFramework::EditorEntityContextNotificationBus::Handler::BusConnect();
}
+ xcb_connection_t* GetXcbConnectionFromQt();
+
+ ///////////////////////////////////////////////////////////////////////
+ // AzToolsFramework::EditorEntityContextNotificationBus overrides
+ void OnStartPlayInEditor() override;
+ void OnStopPlayInEditor() override;
+
// QAbstractNativeEventFilter:
bool nativeEventFilter(const QByteArray& eventType, void* message, long* result) override;
};
diff --git a/Code/Framework/AzCore/AzCore/Asset/AssetManagerBus.h b/Code/Framework/AzCore/AzCore/Asset/AssetManagerBus.h
index f8e263d6b0..f76ea19589 100644
--- a/Code/Framework/AzCore/AzCore/Asset/AssetManagerBus.h
+++ b/Code/Framework/AzCore/AzCore/Asset/AssetManagerBus.h
@@ -65,8 +65,35 @@ namespace AZ
//////////////////////////////////////////////////////////////////////////
// EBusTraits overrides - Application is a singleton
- static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
- typedef AZStd::recursive_mutex MutexType;
+ static constexpr AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
+ using MutexType = AZStd::recursive_mutex;
+
+ static constexpr bool EnableEventQueue = true;
+ using EventQueueMutexType = AZStd::mutex;
+ struct PostThreadDispatchInvoker
+ {
+ ~PostThreadDispatchInvoker();
+ };
+
+ template
+ struct ThreadDispatchLockGuard
+ {
+ ThreadDispatchLockGuard(DispatchMutex& contextMutex)
+ : m_lock{ contextMutex }
+ {}
+ ThreadDispatchLockGuard(DispatchMutex& contextMutex, AZStd::adopt_lock_t adopt_lock)
+ : m_lock{ contextMutex, adopt_lock }
+ {}
+ ThreadDispatchLockGuard(const ThreadDispatchLockGuard&) = delete;
+ ThreadDispatchLockGuard& operator=(const ThreadDispatchLockGuard&) = delete;
+ private:
+ PostThreadDispatchInvoker m_threadPolicyInvoker;
+ using LockType = AZStd::conditional_t, AZStd::scoped_lock>;
+ LockType m_lock;
+ };
+
+ template
+ using DispatchLockGuard = ThreadDispatchLockGuard;
//////////////////////////////////////////////////////////////////////////
virtual ~AssetCatalogRequests() = default;
@@ -200,6 +227,17 @@ namespace AZ
using AssetCatalogRequestBus = AZ::EBus;
+ inline AssetCatalogRequests::PostThreadDispatchInvoker::~PostThreadDispatchInvoker()
+ {
+ if (!AssetCatalogRequestBus::IsInDispatchThisThread())
+ {
+ if (AssetCatalogRequestBus::QueuedEventCount())
+ {
+ AssetCatalogRequestBus::ExecuteQueuedEvents();
+ }
+ }
+ }
+
/*
* Events that AssetManager listens for
*/
diff --git a/Code/Framework/AzCore/AzCore/Component/ComponentApplication.cpp b/Code/Framework/AzCore/AzCore/Component/ComponentApplication.cpp
index ba66d56379..ad7e44c2ae 100644
--- a/Code/Framework/AzCore/AzCore/Component/ComponentApplication.cpp
+++ b/Code/Framework/AzCore/AzCore/Component/ComponentApplication.cpp
@@ -14,6 +14,7 @@
#include
#include
+#include
#include
#include
@@ -44,8 +45,6 @@
#include
#include
-#include
-#include
#include
#include
@@ -215,12 +214,6 @@ namespace AZ
// Update old Project path before attempting to merge in new Settings Registry values in order to prevent recursive calls
m_oldProjectPath = newProjectPath;
- // Merge the project.json file into settings registry under ProjectSettingsRootKey path.
- AZ::IO::FixedMaxPath projectMetadataFile{ AZ::SettingsRegistryMergeUtils::FindEngineRoot(m_registry) / newProjectPath };
- projectMetadataFile /= "project.json";
- m_registry.MergeSettingsFile(projectMetadataFile.Native(),
- AZ::SettingsRegistryInterface::Format::JsonMergePatch, AZ::SettingsRegistryMergeUtils::ProjectSettingsRootKey);
-
// Update all the runtime file paths based on the new "project_path" value.
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(m_registry);
}
@@ -506,6 +499,16 @@ namespace AZ
SettingsRegistryMergeUtils::MergeSettingsToRegistry_CommandLine(*m_settingsRegistry, m_commandLine, executeRegDumpCommands);
SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*m_settingsRegistry);
+ // The /O3DE/Application/LifecycleEvents array contains a valid set of lifecycle events
+ // Those lifecycle events are normally read from the /Registry
+ // which isn't merged until ComponentApplication::Create invokes MergeSettingsToRegistry
+ // So pre-populate the valid lifecycle even entries
+ ComponentApplicationLifecycle::RegisterEvent(*m_settingsRegistry, "SystemAllocatorCreated");
+ ComponentApplicationLifecycle::RegisterEvent(*m_settingsRegistry, "SettingsRegistryAvailable");
+ ComponentApplicationLifecycle::RegisterEvent(*m_settingsRegistry, "ConsoleAvailable");
+ ComponentApplicationLifecycle::SignalEvent(*m_settingsRegistry, "SystemAllocatorCreated", R"({})");
+ ComponentApplicationLifecycle::SignalEvent(*m_settingsRegistry, "SettingsRegistryAvailable", R"({})");
+
// Create the Module Manager
m_moduleManager = AZStd::make_unique();
@@ -520,6 +523,7 @@ namespace AZ
m_ownsConsole = true;
m_console->LinkDeferredFunctors(AZ::ConsoleFunctorBase::GetDeferredHead());
m_settingsRegistryConsoleFunctors = AZ::SettingsRegistryConsoleUtils::RegisterAzConsoleCommands(*m_settingsRegistry, *m_console);
+ ComponentApplicationLifecycle::SignalEvent(*m_settingsRegistry, "ConsoleAvailable", R"({})");
}
}
@@ -551,6 +555,7 @@ namespace AZ
{
AZ::Interface::Unregister(m_console);
delete m_console;
+ ComponentApplicationLifecycle::SignalEvent(*m_settingsRegistry, "ConsoleUnavailable", R"({})");
}
m_moduleManager.reset();
@@ -558,6 +563,8 @@ namespace AZ
if (AZ::SettingsRegistry::Get() == m_settingsRegistry.get())
{
SettingsRegistry::Unregister(m_settingsRegistry.get());
+ ComponentApplicationLifecycle::SignalEvent(*m_settingsRegistry, "SettingsRegistryUnavailable", R"({})");
+ ComponentApplicationLifecycle::SignalEvent(*m_settingsRegistry, "SystemAllocatorPendingDestruction", R"({})");
}
m_settingsRegistry.reset();
@@ -672,6 +679,8 @@ namespace AZ
ReflectionEnvironment::GetReflectionManager()->Reflect(azrtti_typeid(this), [this](ReflectContext* context) {Reflect(context); });
RegisterCoreComponents();
+ ComponentApplicationLifecycle::SignalEvent(*m_settingsRegistry, "ReflectionManagerAvailable", R"({})");
+
TickBus::AllowFunctionQueuing(true);
SystemTickBus::AllowFunctionQueuing(true);
@@ -691,6 +700,7 @@ namespace AZ
// Load the actual modules
LoadModules();
+ ComponentApplicationLifecycle::SignalEvent(*m_settingsRegistry, "GemsLoaded", R"({})");
// Execute user.cfg after modules have been loaded but before processing any command-line overrides
AZ::IO::FixedMaxPath platformCachePath;
@@ -756,12 +766,14 @@ namespace AZ
m_entities.rehash(0); // force free all memory
DestroyReflectionManager();
+ ComponentApplicationLifecycle::SignalEvent(*m_settingsRegistry, "ReflectionManagerUnavailable", R"({})");
static_cast(m_settingsRegistry.get())->ClearNotifiers();
static_cast(m_settingsRegistry.get())->ClearMergeEvents();
// Uninit and unload any dynamic modules.
m_moduleManager->UnloadModules();
+ ComponentApplicationLifecycle::SignalEvent(*m_settingsRegistry, "GemsUnloaded", R"({})");
NameDictionary::Destroy();
diff --git a/Code/Framework/AzCore/AzCore/Component/ComponentApplication.h b/Code/Framework/AzCore/AzCore/Component/ComponentApplication.h
index f2b1bb8905..6df93aff4e 100644
--- a/Code/Framework/AzCore/AzCore/Component/ComponentApplication.h
+++ b/Code/Framework/AzCore/AzCore/Component/ComponentApplication.h
@@ -175,6 +175,8 @@ namespace AZ
bool m_loadDynamicModules = true;
//! Used by test fixtures to ensure reflection occurs to edit context.
bool m_createEditContext = false;
+ //! Indicates whether the AssetCatalog.xml should be loaded by default in Application::StartCommon
+ bool m_loadAssetCatalog = true;
};
ComponentApplication();
@@ -356,7 +358,7 @@ namespace AZ
/// Calculates the root directory of the engine.
void CalculateEngineRoot();
- /// Calculates the directory where the bootstrap.cfg file resides.
+ /// Deprecated: The term "AppRoot" has no meaning
void CalculateAppRoot();
template
diff --git a/Code/Framework/AzCore/AzCore/Component/ComponentApplicationLifecycle.cpp b/Code/Framework/AzCore/AzCore/Component/ComponentApplicationLifecycle.cpp
new file mode 100644
index 0000000000..8e15871bc0
--- /dev/null
+++ b/Code/Framework/AzCore/AzCore/Component/ComponentApplicationLifecycle.cpp
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#include
+#include
+
+namespace AZ::ComponentApplicationLifecycle
+{
+ bool ValidateEvent(AZ::SettingsRegistryInterface& settingsRegistry, AZStd::string_view eventName)
+ {
+ using FixedValueString = SettingsRegistryInterface::FixedValueString;
+ using Type = SettingsRegistryInterface::Type;
+ FixedValueString eventRegistrationKey{ ApplicationLifecycleEventRegistrationKey };
+ eventRegistrationKey += '/';
+ eventRegistrationKey += eventName;
+ return settingsRegistry.GetType(eventRegistrationKey) == Type::Object;
+ }
+
+ bool SignalEvent(AZ::SettingsRegistryInterface& settingsRegistry, AZStd::string_view eventName, AZStd::string_view eventValue)
+ {
+ using FixedValueString = AZ::SettingsRegistryInterface::FixedValueString;
+ using Format = AZ::SettingsRegistryInterface::Format;
+
+ if (!ValidateEvent(settingsRegistry, eventName))
+ {
+ AZ_Warning("ComponentApplicationLifecycle", false, R"(Cannot signal event %.*s. Name does is not a field of object "%.*s".)"
+ R"( Please make sure the entry exists in the '/Registry/application_lifecycle_events.setreg")"
+ " or in *.setreg within the project", AZ_STRING_ARG(eventName), AZ_STRING_ARG(ApplicationLifecycleEventRegistrationKey));
+ return false;
+ }
+ auto eventRegistrationKey = FixedValueString::format("%.*s/%.*s", AZ_STRING_ARG(ApplicationLifecycleEventRegistrationKey),
+ AZ_STRING_ARG(eventName));
+
+ return settingsRegistry.MergeSettings(eventValue, Format::JsonMergePatch, eventRegistrationKey);
+ }
+
+ bool RegisterEvent(AZ::SettingsRegistryInterface& settingsRegistry, AZStd::string_view eventName)
+ {
+ using FixedValueString = SettingsRegistryInterface::FixedValueString;
+ using Format = AZ::SettingsRegistryInterface::Format;
+
+ if (!ValidateEvent(settingsRegistry, eventName))
+ {
+ FixedValueString eventRegistrationKey{ ApplicationLifecycleEventRegistrationKey };
+ eventRegistrationKey += '/';
+ eventRegistrationKey += eventName;
+ return settingsRegistry.MergeSettings(R"({})", Format::JsonMergePatch, eventRegistrationKey);
+ }
+
+ return true;
+ }
+
+ bool RegisterHandler(AZ::SettingsRegistryInterface& settingsRegistry, AZ::SettingsRegistryInterface::NotifyEventHandler& handler,
+ AZ::SettingsRegistryInterface::NotifyCallback callback, AZStd::string_view eventName, bool autoRegisterEvent)
+ {
+ using FixedValueString = AZ::SettingsRegistryInterface::FixedValueString;
+ using Type = AZ::SettingsRegistryInterface::Type;
+ using NotifyEventHandler = AZ::SettingsRegistryInterface::NotifyEventHandler;
+
+ // Some systems may attempt to register a handler before the settings registry has been loaded
+ // If so, this flag lets them automatically register an event if it hasn't yet been registered.
+ // RegisterEvent calls validate event.
+ if ((!autoRegisterEvent && !ValidateEvent(settingsRegistry, eventName)) ||
+ (autoRegisterEvent && !RegisterEvent(settingsRegistry, eventName)))
+ {
+ AZ_Warning(
+ "ComponentApplicationLifecycle", false,
+ R"(Cannot register event %.*s. Name is not a field of object "%.*s".)"
+ R"( Please make sure the entry exists in the '/Registry/application_lifecycle_events.setreg")"
+ " or in *.setreg within the project", AZ_STRING_ARG(eventName), AZ_STRING_ARG(ApplicationLifecycleEventRegistrationKey));
+ return false;
+ }
+ auto eventNameRegistrationKey = FixedValueString::format("%.*s/%.*s", AZ_STRING_ARG(ApplicationLifecycleEventRegistrationKey),
+ AZ_STRING_ARG(eventName));
+ auto lifecycleCallback = [callback = AZStd::move(callback), eventNameRegistrationKey](AZStd::string_view path, Type type)
+ {
+ if (path == eventNameRegistrationKey)
+ {
+ callback(path, type);
+ }
+ };
+
+ handler = NotifyEventHandler(AZStd::move(lifecycleCallback));
+ settingsRegistry.RegisterNotifier(handler);
+
+ return true;
+ }
+}
diff --git a/Code/Framework/AzCore/AzCore/Component/ComponentApplicationLifecycle.h b/Code/Framework/AzCore/AzCore/Component/ComponentApplicationLifecycle.h
new file mode 100644
index 0000000000..6f07c0da3f
--- /dev/null
+++ b/Code/Framework/AzCore/AzCore/Component/ComponentApplicationLifecycle.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#pragma once
+
+#include
+#include
+
+namespace AZ::ComponentApplicationLifecycle
+{
+ //! Root Key where lifecycle events should be registered under
+ inline constexpr AZStd::string_view ApplicationLifecycleEventRegistrationKey = "/O3DE/Application/LifecycleEvents";
+
+
+ //! Validates that the event @eventName is stored in the array at ApplicationLifecycleEventRegistrationKey
+ //! @param settingsRegistry registry where @eventName will be searched
+ //! @param eventName name of key that validated that exists as an element in the ApplicationLifecycleEventRegistrationKey array
+ //! @return true if the @eventName was found in the ApplicationLifecycleEventRegistrationKey array
+ bool ValidateEvent(AZ::SettingsRegistryInterface& settingsRegistry, AZStd::string_view eventName);
+
+ //! Wrapper around setting a value underneath the ApplicationLifecycleEventRegistrationKey
+ //! It validates if the @eventName is is part of the ApplicationLifecycleEventRegistrationKey array
+ //! It then appends the @eventName to the ApplicationLifecycleEventRegistrationKey merges the @eventValue into
+ //! the SettingsRegistry at that key
+ //! NOTE: This function should only be invoked from ComponentApplication and its derived classes
+ //! @param settingsRegistry registry where eventName should be set
+ //! @param eventName name of key underneath the ApplicationLifecycleEventRegistrationKey to signal
+ //! @param eventValue JSON Object that will be merged into the SettingsRegistry at /
+ //! @return true if the eventValue was successfully merged at the /
+ bool SignalEvent(AZ::SettingsRegistryInterface& settingsRegistry, AZStd::string_view eventName, AZStd::string_view eventValue);
+
+ //! Register that the event @eventName is stored in the array at ApplicationLifecycleEventRegistrationKey
+ //! @param settingsRegistry registry where @eventName will be searched
+ //! @param eventName name of key that will be stored in the ApplicationLifecycleEventRegistrationKey array
+ //! @return true if the event passed validation or the eventName was stored in the ApplicationLifecycleEventRegistrationKey array
+ bool RegisterEvent(AZ::SettingsRegistryInterface& settingsRegistry, AZStd::string_view eventName);
+
+ //! Wrapper around registering the NotifyEventHandler with the SettingsRegistry for the specified event
+ //! It validates if the @eventName is is part of the ApplicationLifecycleEventRegistrationKey array and if
+ //! so moves the @callback into @handler and then registers the handler with the SettingsRegistry NotifyEvent
+ //! @param settingsRegistry registry where handler will be registered
+ //! @param handler handler where callback will be moved into and then registered with the SettingsRegistry
+ //! if the specified @eventName passes validation
+ //! @param callback will be moved into the handler if the specified @eventName is valid
+ //! @param eventName name of key underneath the ApplicationLifecycleEventRegistrationKey to register
+ //! @param autoRegisterEvent automatically register this event if it hasn't been registered yet. This is useful
+ //! when registering a handler before the settings registry has been loaded.
+ //! @return true if the handler was registered with the SettingsRegistry NotifyEvent
+ bool RegisterHandler(AZ::SettingsRegistryInterface& settingsRegistry, AZ::SettingsRegistryInterface::NotifyEventHandler& handler,
+ AZ::SettingsRegistryInterface::NotifyCallback callback, AZStd::string_view eventName, bool autoRegisterEvent = false);
+}
diff --git a/Code/Framework/AzCore/AzCore/Console/IConsole.h b/Code/Framework/AzCore/AzCore/Console/IConsole.h
index 73d17ac65a..aef163d22b 100644
--- a/Code/Framework/AzCore/AzCore/Console/IConsole.h
+++ b/Code/Framework/AzCore/AzCore/Console/IConsole.h
@@ -262,6 +262,6 @@ static constexpr AZ::ThreadSafety ConsoleThreadSafety<_TYPE, std::enable_if_t Functor##_FUNCTION(#_FUNCTION, _DESC, _FLAGS | AZ::ConsoleFunctorFlags::DontDuplicate, AZ::TypeId::CreateNull(), &_FUNCTION)
+ inline AZ::ConsoleFunctor Functor##_FUNCTION(_NAME, _DESC, _FLAGS | AZ::ConsoleFunctorFlags::DontDuplicate, AZ::TypeId::CreateNull(), &_FUNCTION)
#define AZ_CONSOLEFREEFUNC(...) AZ_MACRO_SPECIALIZE(AZ_CONSOLEFREEFUNC_, AZ_VA_NUM_ARGS(__VA_ARGS__), (__VA_ARGS__))
diff --git a/Code/Framework/AzCore/AzCore/Module/ModuleManager.cpp b/Code/Framework/AzCore/AzCore/Module/ModuleManager.cpp
index 1fdeafa309..cbf3ce5243 100644
--- a/Code/Framework/AzCore/AzCore/Module/ModuleManager.cpp
+++ b/Code/Framework/AzCore/AzCore/Module/ModuleManager.cpp
@@ -10,16 +10,13 @@
#include
#include
-#include
-#include
#include
#include
#include
-#include
#include
+#include
+#include
#include
-#include
-#include
#include
#include
@@ -221,11 +218,16 @@ namespace AZ
}
}
+ AZStd::string componentNamesArray = R"({ "SystemComponents":[)";
+ const char* comma = "";
// For all system components, deactivate
for (auto componentIt = m_systemComponents.rbegin(); componentIt != m_systemComponents.rend(); ++componentIt)
{
ModuleEntity::DeactivateComponent(**componentIt);
+ componentNamesArray += AZStd::string::format(R"(%s"%s")", comma, (*componentIt)->RTTI_GetTypeName());
+ comma = ", ";
}
+ componentNamesArray += R"(]})";
// For all modules that we created an entity for, set them to "Init" (meaning not Activated)
for (auto& moduleData : m_ownedModules)
@@ -239,6 +241,13 @@ namespace AZ
// Since the system components have been deactivated clear out the vector.
m_systemComponents.clear();
+
+ // Signal that the System Components have deactivated
+ if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
+ {
+ AZ::ComponentApplicationLifecycle::SignalEvent(*settingsRegistry, "SystemComponentsDeactivated", componentNamesArray);
+ }
+
}
//=========================================================================
@@ -284,7 +293,11 @@ namespace AZ
{
// Split the tag list
AZStd::vector tagList;
- AZStd::tokenize(tags, ",", tagList);
+ auto TokenizeTags = [&tagList](AZStd::string_view token)
+ {
+ tagList.push_back(token);
+ };
+ AZ::StringFunc::TokenizeVisitor(tags, TokenizeTags, ',');
m_systemComponentTags.resize(tagList.size());
AZStd::transform(tagList.begin(), tagList.end(), m_systemComponentTags.begin(), [](const AZStd::string_view& tag)
@@ -737,11 +750,17 @@ namespace AZ
}
}
+ AZStd::string componentNamesArray = R"({ "SystemComponents":[)";
+ const char* comma = "";
// Activate the entities in the appropriate order
for (Component* component : componentsToActivate)
{
ModuleEntity::ActivateComponent(*component);
+
+ componentNamesArray += AZStd::string::format(R"(%s"%s")", comma, component->RTTI_GetTypeName());
+ comma = ", ";
}
+ componentNamesArray += R"(]})";
// Done activating; set state to active
for (auto& moduleData : modulesToInit)
@@ -755,5 +774,12 @@ namespace AZ
// Save the activated components for deactivation later
m_systemComponents.insert(m_systemComponents.end(), componentsToActivate.begin(), componentsToActivate.end());
+
+ // Signal that the System Components are activated
+ if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
+ {
+ AZ::ComponentApplicationLifecycle::SignalEvent(*settingsRegistry, "SystemComponentsActivated",
+ componentNamesArray);
+ }
}
} // namespace AZ
diff --git a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.cpp b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.cpp
index 36f66312d8..3668ab14fd 100644
--- a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.cpp
+++ b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.cpp
@@ -29,6 +29,8 @@
namespace AZ::Internal
{
+ static constexpr const char* ProductCacheDirectoryName = "Cache";
+
AZ::SettingsRegistryInterface::FixedValueString GetEngineMonikerForProject(
SettingsRegistryInterface& settingsRegistry, const AZ::IO::FixedMaxPath& projectJsonPath)
{
@@ -228,19 +230,20 @@ namespace AZ::Internal
namespace AZ::SettingsRegistryMergeUtils
{
- constexpr AZStd::string_view InternalScanUpEngineRootKey{ "/O3DE/Settings/Internal/engine_root_scan_up_path" };
- constexpr AZStd::string_view InternalScanUpProjectRootKey{ "/O3DE/Settings/Internal/project_root_scan_up_path" };
-
AZ::IO::FixedMaxPath FindEngineRoot(SettingsRegistryInterface& settingsRegistry)
{
+ static constexpr AZStd::string_view InternalScanUpEngineRootKey{ "/O3DE/Runtime/Internal/engine_root_scan_up_path" };
+ using FixedValueString = SettingsRegistryInterface::FixedValueString;
+ using Type = SettingsRegistryInterface::Type;
+
AZ::IO::FixedMaxPath engineRoot;
// This is the 'external' engine root key, as in passed from command-line or .setreg files.
- auto engineRootKey = SettingsRegistryInterface::FixedValueString::format("%s/engine_path", BootstrapSettingsRootKey);
+ constexpr auto engineRootKey = FixedValueString(BootstrapSettingsRootKey) + "/engine_path";
// Step 1 Run the scan upwards logic once to find the location of the engine.json if it exist
// Once this step is run the {InternalScanUpEngineRootKey} is set in the Settings Registry
// to have this scan logic only run once InternalScanUpEngineRootKey the supplied registry
- if (settingsRegistry.GetType(InternalScanUpEngineRootKey) == SettingsRegistryInterface::Type::NoType)
+ if (settingsRegistry.GetType(InternalScanUpEngineRootKey) == Type::NoType)
{
// We can scan up from exe directory to find engine.json, use that for engine root if it exists.
engineRoot = Internal::ScanUpRootLocator("engine.json");
@@ -283,14 +286,18 @@ namespace AZ::SettingsRegistryMergeUtils
AZ::IO::FixedMaxPath FindProjectRoot(SettingsRegistryInterface& settingsRegistry)
{
+ static constexpr AZStd::string_view InternalScanUpProjectRootKey{ "/O3DE/Runtime/Internal/project_root_scan_up_path" };
+ using FixedValueString = SettingsRegistryInterface::FixedValueString;
+ using Type = SettingsRegistryInterface::Type;
+
AZ::IO::FixedMaxPath projectRoot;
- const auto projectRootKey = SettingsRegistryInterface::FixedValueString::format("%s/project_path", BootstrapSettingsRootKey);
+ constexpr auto projectRootKey = FixedValueString(BootstrapSettingsRootKey) + "/project_path";
- // Step 1 Run the scan upwards logic once to find the location of the project.json if it exist
+ // Step 1 Run the scan upwards logic once to find the location of the closest ancestor project.json
// Once this step is run the {InternalScanUpProjectRootKey} is set in the Settings Registry
// to have this scan logic only run once for the supplied registry
// SettingsRegistryInterface::GetType is used to check if a key is set
- if (settingsRegistry.GetType(InternalScanUpProjectRootKey) == SettingsRegistryInterface::Type::NoType)
+ if (settingsRegistry.GetType(InternalScanUpProjectRootKey) == Type::NoType)
{
projectRoot = Internal::ScanUpRootLocator("project.json");
// Set the {InternalScanUpProjectRootKey} to make sure this code path isn't called again for this settings registry
@@ -305,19 +312,129 @@ namespace AZ::SettingsRegistryMergeUtils
}
// Step 2 Check the project-path key
- // This is the project path root key, as in passed from command-line or .setreg files.
- if (settingsRegistry.Get(projectRoot.Native(), projectRootKey))
+ // This is the project path root key, as passed from command-line or *.setreg files.
+ settingsRegistry.Get(projectRoot.Native(), projectRootKey);
+ return projectRoot;
+ }
+
+ //! The algorithm that is used to find the project cache is as follows
+ //! 1. The "{BootstrapSettingsRootKey}/project_cache_path" is checked for the path
+ //! 2. Otherwise append the ProductCacheDirectoryName constant to the
+ static AZ::IO::FixedMaxPath FindProjectCachePath(SettingsRegistryInterface& settingsRegistry, const AZ::IO::FixedMaxPath& projectPath)
+ {
+ using FixedValueString = SettingsRegistryInterface::FixedValueString;
+
+ constexpr auto projectCachePathKey = FixedValueString(BootstrapSettingsRootKey) + "/project_cache_path";
+
+ // Step 1 Check the project-cache-path key
+ if (AZ::IO::FixedMaxPath projectCachePath; settingsRegistry.Get(projectCachePath.Native(), projectCachePathKey))
{
- return projectRoot;
+ return projectCachePath;
}
- // Step 3 Check for a "Cache" directory by scanning upwards from the executable directory
- if (auto candidateRoot = Internal::ScanUpRootLocator("Cache");
- !candidateRoot.empty() && AZ::IO::SystemFile::IsDirectory(candidateRoot.c_str()))
+ // Step 2 Append the "Cache" directory to the project-path
+ return projectPath / Internal::ProductCacheDirectoryName;
+ }
+
+ //! Set the user directory with the provided path or using /user as default
+ static AZ::IO::FixedMaxPath FindProjectUserPath(SettingsRegistryInterface& settingsRegistry,
+ const AZ::IO::FixedMaxPath& projectPath)
+ {
+ using FixedValueString = SettingsRegistryInterface::FixedValueString;
+
+ // User: root - same as the @user@ alias, this is the starting path for transient data and log files.
+ constexpr auto projectUserPathKey = FixedValueString(BootstrapSettingsRootKey) + "/project_user_path";
+
+ // Step 1 Check the project-user-path key
+ if (AZ::IO::FixedMaxPath projectUserPath; settingsRegistry.Get(projectUserPath.Native(), projectUserPathKey))
{
- projectRoot = AZStd::move(candidateRoot);
+ return projectUserPath;
+ }
+
+ // Step 2 Append the "User" directory to the project-path
+ return projectPath / "user";
+ }
+
+ //! Set the log directory using the settings registry path or using /log as default
+ static AZ::IO::FixedMaxPath FindProjectLogPath(SettingsRegistryInterface& settingsRegistry,
+ const AZ::IO::FixedMaxPath& projectUserPath)
+ {
+ using FixedValueString = SettingsRegistryInterface::FixedValueString;
+
+ // User: root - same as the @log@ alias, this is the starting path for transient data and log files.
+ constexpr auto projectLogPathKey = FixedValueString(BootstrapSettingsRootKey) + "/project_log_path";
+
+ // Step 1 Check the project-user-path key
+ if (AZ::IO::FixedMaxPath projectLogPath; settingsRegistry.Get(projectLogPath.Native(), projectLogPathKey))
+ {
+ return projectLogPath;
+ }
+
+ // Step 2 Append the "Log" directory to the project-user-path
+ return projectUserPath / "log";
+ }
+
+ // check for a default write storage path, fall back to the if not
+ static AZ::IO::FixedMaxPath FindDevWriteStoragePath(const AZ::IO::FixedMaxPath& projectUserPath)
+ {
+ AZStd::optional devWriteStorage = Utils::GetDevWriteStoragePath();
+ return devWriteStorage.has_value() ? *devWriteStorage : projectUserPath;
+ }
+
+ // check for the project build path, which is a relative path from the project root
+ // that specifies where the build directory is located
+ static void SetProjectBuildPath(SettingsRegistryInterface& settingsRegistry,
+ const AZ::IO::FixedMaxPath& projectPath)
+ {
+ if (AZ::IO::FixedMaxPath projectBuildPath; settingsRegistry.Get(projectBuildPath.Native(), ProjectBuildPath))
+ {
+ settingsRegistry.Remove(FilePathKey_ProjectBuildPath);
+ settingsRegistry.Remove(FilePathKey_ProjectConfigurationBinPath);
+ AZ::IO::FixedMaxPath buildConfigurationPath = (projectPath / projectBuildPath).LexicallyNormal();
+ if (IO::SystemFile::Exists(buildConfigurationPath.c_str()))
+ {
+ settingsRegistry.Set(FilePathKey_ProjectBuildPath, buildConfigurationPath.Native());
+ }
+
+ // Add the specific build configuration paths to the Settings Registry
+ // First try /bin/$ and if that path doesn't exist
+ // try /bin/$/$
+ buildConfigurationPath /= "bin";
+ if (IO::SystemFile::Exists((buildConfigurationPath / AZ_BUILD_CONFIGURATION_TYPE).c_str()))
+ {
+ settingsRegistry.Set(FilePathKey_ProjectConfigurationBinPath,
+ (buildConfigurationPath / AZ_BUILD_CONFIGURATION_TYPE).Native());
+ }
+ else if (IO::SystemFile::Exists((buildConfigurationPath / AZ_TRAIT_OS_PLATFORM_CODENAME / AZ_BUILD_CONFIGURATION_TYPE).c_str()))
+ {
+ settingsRegistry.Set(FilePathKey_ProjectConfigurationBinPath,
+ (buildConfigurationPath / AZ_TRAIT_OS_PLATFORM_CODENAME / AZ_BUILD_CONFIGURATION_TYPE).Native());
+ }
+ }
+ }
+
+ // Sets the project name within the Settings Registry by looking up the "project_name"
+ // within the project.json file
+ static void SetProjectName(SettingsRegistryInterface& settingsRegistry,
+ const AZ::IO::FixedMaxPath& projectPath)
+ {
+ using FixedValueString = SettingsRegistryInterface::FixedValueString;
+ // Project name - if it was set via merging project.json use that value, otherwise use the project path's folder name.
+ constexpr auto projectNameKey = FixedValueString(ProjectSettingsRootKey) + "/project_name";
+
+ // Read the project name from the project.json file if it exists
+ if (AZ::IO::FixedMaxPath projectJsonPath = projectPath / "project.json";
+ AZ::IO::SystemFile::Exists(projectJsonPath.c_str()))
+ {
+ settingsRegistry.MergeSettingsFile(projectJsonPath.Native(),
+ AZ::SettingsRegistryInterface::Format::JsonMergePatch, AZ::SettingsRegistryMergeUtils::ProjectSettingsRootKey);
+ }
+ // If a project name isn't set the default will be set to the final path segment of the project path
+ if (FixedValueString projectName; !settingsRegistry.Get(projectName, projectNameKey))
+ {
+ projectName = projectPath.Filename().Native();
+ settingsRegistry.Set(projectNameKey, projectName);
}
- return projectRoot;
}
AZStd::string_view ConfigParserSettings::DefaultCommentPrefixFilter(AZStd::string_view line)
@@ -397,7 +514,7 @@ namespace AZ::SettingsRegistryMergeUtils
bool MergeSettingsToRegistry_ConfigFile(SettingsRegistryInterface& registry, AZStd::string_view filePath,
const ConfigParserSettings& configParserSettings)
{
- auto configPath = FindEngineRoot(registry) / filePath;
+ auto configPath = FindProjectRoot(registry) / filePath;
IO::FileReader configFile;
bool configFileOpened{};
switch (configParserSettings.m_fileReaderClass)
@@ -542,19 +659,77 @@ namespace AZ::SettingsRegistryMergeUtils
void MergeSettingsToRegistry_AddRuntimeFilePaths(SettingsRegistryInterface& registry)
{
using FixedValueString = AZ::SettingsRegistryInterface::FixedValueString;
- // Binary folder
- AZ::IO::FixedMaxPath path = AZ::Utils::GetExecutableDirectory();
- registry.Set(FilePathKey_BinaryFolder, path.LexicallyNormal().Native());
- // Engine root folder - corresponds to the @engroot@ and @engroot@ aliases
+ // Binary folder - corresponds to the @exefolder@ alias
+ AZ::IO::FixedMaxPath exePath = AZ::Utils::GetExecutableDirectory();
+ registry.Set(FilePathKey_BinaryFolder, exePath.LexicallyNormal().Native());
+
+ // Project path - corresponds to the @projectroot@ alias
+ // NOTE: We make the project-path in the BootstrapSettingsRootKey absolute first
+
+ AZ::IO::FixedMaxPath projectPath = FindProjectRoot(registry);
+ if (constexpr auto projectPathKey = FixedValueString(BootstrapSettingsRootKey) + "/project_path";
+ !projectPath.empty())
+ {
+ if (projectPath.IsRelative())
+ {
+ if (auto projectAbsPath = AZ::Utils::ConvertToAbsolutePath(projectPath.Native());
+ projectAbsPath.has_value())
+ {
+ projectPath = AZStd::move(*projectAbsPath);
+ }
+ }
+
+ projectPath = projectPath.LexicallyNormal();
+ AZ_Warning("SettingsRegistryMergeUtils", AZ::IO::SystemFile::Exists(projectPath.c_str()),
+ R"(Project path "%s" does not exist. Is the "%.*s" registry setting set to a valid absolute path?)"
+ , projectPath.c_str(), AZ_STRING_ARG(projectPathKey));
+
+ registry.Set(FilePathKey_ProjectPath, projectPath.Native());
+ }
+ else
+ {
+ AZ_TracePrintf("SettingsRegistryMergeUtils",
+ R"(Project path isn't set in the Settings Registry at "%.*s".)"
+ " Project-related filepaths will be set relative to the executable directory\n",
+ AZ_STRING_ARG(projectPathKey));
+ registry.Set(FilePathKey_ProjectPath, exePath.Native());
+ }
+
+ // Engine root folder - corresponds to the @engroot@ alias
AZ::IO::FixedMaxPath engineRoot = FindEngineRoot(registry);
- registry.Set(FilePathKey_EngineRootFolder, engineRoot.LexicallyNormal().Native());
+ if (!engineRoot.empty())
+ {
+ if (engineRoot.IsRelative())
+ {
+ if (auto engineRootAbsPath = AZ::Utils::ConvertToAbsolutePath(engineRoot.Native());
+ engineRootAbsPath.has_value())
+ {
+ engineRoot = AZStd::move(*engineRootAbsPath);
+ }
+ }
+
+ engineRoot = engineRoot.LexicallyNormal();
+ registry.Set(FilePathKey_EngineRootFolder, engineRoot.Native());
+ }
- auto projectPathKey = FixedValueString::format("%s/project_path", BootstrapSettingsRootKey);
- SettingsRegistryInterface::FixedValueString projectPathValue;
- if (registry.Get(projectPathValue, projectPathKey))
+ // Cache folder
+ AZ::IO::FixedMaxPath projectCachePath = FindProjectCachePath(registry, projectPath).LexicallyNormal();
+ if (!projectCachePath.empty())
{
- // Cache folder
+ if (projectCachePath.IsRelative())
+ {
+ if (auto projectCacheAbsPath = AZ::Utils::ConvertToAbsolutePath(projectCachePath.Native());
+ projectCacheAbsPath.has_value())
+ {
+ projectCachePath = AZStd::move(*projectCacheAbsPath);
+ }
+ }
+
+ projectCachePath = projectCachePath.LexicallyNormal();
+ registry.Set(FilePathKey_CacheProjectRootFolder, projectCachePath.Native());
+
+ // Cache/ folder
// Get the name of the asset platform assigned by the bootstrap. First check for platform version such as "windows_assets"
// and if that's missing just get "assets".
FixedValueString assetPlatform;
@@ -570,118 +745,67 @@ namespace AZ::SettingsRegistryMergeUtils
assetPlatform = AZ::OSPlatformToDefaultAssetPlatform(AZ_TRAIT_OS_PLATFORM_CODENAME);
}
- // Project path - corresponds to the @projectroot@ alias
- // NOTE: Here we append to engineRoot, but if projectPathValue is absolute then engineRoot is discarded.
- path = engineRoot / projectPathValue;
-
- AZ_Warning("SettingsRegistryMergeUtils", AZ::IO::SystemFile::Exists(path.c_str()),
- R"(Project path "%s" does not exist. Is the "%.*s" registry setting set to valid absolute path?)"
- , path.c_str(), aznumeric_cast(projectPathKey.size()), projectPathKey.data());
-
- AZ::IO::FixedMaxPath normalizedProjectPath = path.LexicallyNormal();
- registry.Set(FilePathKey_ProjectPath, normalizedProjectPath.Native());
-
- // Set the user directory with the provided path or using project/user as default
- auto projectUserPathKey = FixedValueString::format("%s/project_user_path", BootstrapSettingsRootKey);
- AZ::IO::FixedMaxPath projectUserPath;
- if (!registry.Get(projectUserPath.Native(), projectUserPathKey))
+ // Make sure the asset platform is set before setting cache path for the asset platform.
+ if (!assetPlatform.empty())
{
- projectUserPath = (normalizedProjectPath / "user").LexicallyNormal();
+ registry.Set(FilePathKey_CacheRootFolder, (projectCachePath / assetPlatform).Native());
}
- registry.Set(FilePathKey_ProjectUserPath, projectUserPath.Native());
+ }
- // Set the log directory with the provided path or using project/user/log as default
- auto projectLogPathKey = FixedValueString::format("%s/project_log_path", BootstrapSettingsRootKey);
- AZ::IO::FixedMaxPath projectLogPath;
- if (!registry.Get(projectLogPath.Native(), projectLogPathKey))
+ // User folder
+ AZ::IO::FixedMaxPath projectUserPath = FindProjectUserPath(registry, projectPath);
+ if (!projectUserPath.empty())
+ {
+ if (projectUserPath.IsRelative())
{
- projectLogPath = (projectUserPath / "log").LexicallyNormal();
+ if (auto projectUserAbsPath = AZ::Utils::ConvertToAbsolutePath(projectUserPath.Native());
+ projectUserAbsPath.has_value())
+ {
+ projectUserPath = AZStd::move(*projectUserAbsPath);
+ }
}
- registry.Set(FilePathKey_ProjectLogPath, projectLogPath.Native());
- // check for a default write storage path, fall back to the project's user/ directory if not
- AZStd::optional devWriteStorage = Utils::GetDevWriteStoragePath();
- registry.Set(FilePathKey_DevWriteStorage, devWriteStorage.has_value()
- ? devWriteStorage.value()
- : projectUserPath.Native());
+ projectUserPath = projectUserPath.LexicallyNormal();
+ registry.Set(FilePathKey_ProjectUserPath, projectUserPath.Native());
+ }
- // Set the project in-memory build path if the ProjectBuildPath key has been supplied
- if (AZ::IO::FixedMaxPath projectBuildPath; registry.Get(projectBuildPath.Native(), ProjectBuildPath))
+ // Log folder
+ if (AZ::IO::FixedMaxPath projectLogPath = FindProjectLogPath(registry, projectUserPath); !projectLogPath.empty())
+ {
+ if (projectLogPath.IsRelative())
{
- registry.Remove(FilePathKey_ProjectBuildPath);
- registry.Remove(FilePathKey_ProjectConfigurationBinPath);
- AZ::IO::FixedMaxPath buildConfigurationPath = normalizedProjectPath / projectBuildPath;
- if (IO::SystemFile::Exists(buildConfigurationPath.c_str()))
- {
- registry.Set(FilePathKey_ProjectBuildPath, buildConfigurationPath.LexicallyNormal().Native());
- }
-
- // Add the specific build configuration paths to the Settings Registry
- // First try /bin/$ and if that path doesn't exist
- // try /bin/$/$
- buildConfigurationPath /= "bin";
- if (IO::SystemFile::Exists((buildConfigurationPath / AZ_BUILD_CONFIGURATION_TYPE).c_str()))
- {
- registry.Set(FilePathKey_ProjectConfigurationBinPath,
- (buildConfigurationPath / AZ_BUILD_CONFIGURATION_TYPE).LexicallyNormal().Native());
- }
- else if (IO::SystemFile::Exists((buildConfigurationPath / AZ_TRAIT_OS_PLATFORM_CODENAME / AZ_BUILD_CONFIGURATION_TYPE).c_str()))
+ if (auto projectLogAbsPath = AZ::Utils::ConvertToAbsolutePath(projectLogPath.Native()))
{
- registry.Set(FilePathKey_ProjectConfigurationBinPath,
- (buildConfigurationPath / AZ_TRAIT_OS_PLATFORM_CODENAME / AZ_BUILD_CONFIGURATION_TYPE).LexicallyNormal().Native());
+ projectLogPath = AZStd::move(*projectLogAbsPath);
}
-
}
- // Project name - if it was set via merging project.json use that value, otherwise use the project path's folder name.
- auto projectNameKey =
- AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::ProjectSettingsRootKey)
- + "/project_name";
-
- AZ::SettingsRegistryInterface::FixedValueString projectName;
- if (!registry.Get(projectName, projectNameKey))
- {
- projectName = path.Filename().Native();
- registry.Set(projectNameKey, projectName);
- }
+ projectLogPath = projectLogPath.LexicallyNormal();
+ registry.Set(FilePathKey_ProjectLogPath, projectLogPath.Native());
+ }
- // Cache folders - sets up various paths in registry for the cache.
- // Make sure the asset platform is set before setting these cache paths.
- if (!assetPlatform.empty())
+ // Developer Write Storage folder
+ if (AZ::IO::FixedMaxPath devWriteStoragePath = FindDevWriteStoragePath(projectUserPath); !devWriteStoragePath.empty())
+ {
+ if (devWriteStoragePath.IsRelative())
{
- // Cache: project root - no corresponding fileIO alias, but this is where the asset database lives.
- // A registry override is accepted using the "project_cache_path" key.
- auto projectCacheRootOverrideKey = FixedValueString::format("%s/project_cache_path", BootstrapSettingsRootKey);
- // Clear path to make sure that the `project_cache_path` value isn't concatenated to the project path
- path.clear();
- if (registry.Get(path.Native(), projectCacheRootOverrideKey))
- {
- registry.Set(FilePathKey_CacheProjectRootFolder, path.LexicallyNormal().Native());
- path /= assetPlatform;
- registry.Set(FilePathKey_CacheRootFolder, path.LexicallyNormal().Native());
- }
- else
+ if (auto devWriteStorageAbsPath = AZ::Utils::ConvertToAbsolutePath(devWriteStoragePath.Native()))
{
- // Cache: root - same as the @products@ alias, this is the starting path for cache files.
- path = normalizedProjectPath / "Cache";
- registry.Set(FilePathKey_CacheProjectRootFolder, path.LexicallyNormal().Native());
- path /= assetPlatform;
- registry.Set(FilePathKey_CacheRootFolder, path.LexicallyNormal().Native());
+ devWriteStoragePath = AZStd::move(*devWriteStorageAbsPath);
}
}
- }
- else
- {
- // Set the default ProjectUserPath to the /user directory
- registry.Set(FilePathKey_ProjectUserPath, (engineRoot / "user").LexicallyNormal().Native());
- AZ_TracePrintf("SettingsRegistryMergeUtils",
- R"(Project path isn't set in the Settings Registry at "%.*s". Project-related filepaths will not be set)" "\n",
- aznumeric_cast(projectPathKey.size()), projectPathKey.data());
+
+ devWriteStoragePath = devWriteStoragePath.LexicallyNormal();
+ registry.Set(FilePathKey_DevWriteStorage, devWriteStoragePath.Native());
}
+ // Set the project in-memory build path if the ProjectBuildPath key has been supplied
+ SetProjectBuildPath(registry, projectPath);
+ // Set the project name using the "project_name" key
+ SetProjectName(registry, projectPath);
+
#if !AZ_TRAIT_OS_IS_HOST_OS_PLATFORM
// Setup the cache, user, and log paths to platform specific locations when running on non-host platforms
- path = engineRoot;
if (AZStd::optional nonHostCacheRoot = Utils::GetDefaultAppRootPath();
nonHostCacheRoot)
{
@@ -690,25 +814,25 @@ namespace AZ::SettingsRegistryMergeUtils
}
else
{
- registry.Set(FilePathKey_CacheProjectRootFolder, path.LexicallyNormal().Native());
- registry.Set(FilePathKey_CacheRootFolder, path.LexicallyNormal().Native());
+ registry.Set(FilePathKey_CacheProjectRootFolder, projectPath.Native());
+ registry.Set(FilePathKey_CacheRootFolder, projectPath.Native());
}
if (AZStd::optional devWriteStorage = Utils::GetDevWriteStoragePath();
devWriteStorage)
{
- const AZ::IO::FixedMaxPath devWriteStoragePath(*devWriteStorage);
- registry.Set(FilePathKey_DevWriteStorage, devWriteStoragePath.LexicallyNormal().Native());
- registry.Set(FilePathKey_ProjectUserPath, (devWriteStoragePath / "user").LexicallyNormal().Native());
- registry.Set(FilePathKey_ProjectLogPath, (devWriteStoragePath / "user/log").LexicallyNormal().Native());
+ const auto devWriteStoragePath = AZ::IO::PathView(*devWriteStorage).LexicallyNormal();
+ registry.Set(FilePathKey_DevWriteStorage, devWriteStoragePath.Native());
+ registry.Set(FilePathKey_ProjectUserPath, (devWriteStoragePath / "user").Native());
+ registry.Set(FilePathKey_ProjectLogPath, (devWriteStoragePath / "user" / "log").Native());
}
else
{
- registry.Set(FilePathKey_DevWriteStorage, path.LexicallyNormal().Native());
- registry.Set(FilePathKey_ProjectUserPath, (path / "user").LexicallyNormal().Native());
- registry.Set(FilePathKey_ProjectLogPath, (path / "user/log").LexicallyNormal().Native());
- }
-#endif // AZ_TRAIT_OS_IS_HOST_OS_PLATFORM
+ registry.Set(FilePathKey_DevWriteStorage, projectPath.Native());
+ registry.Set(FilePathKey_ProjectUserPath, (projectPath / "user").Native());
+ registry.Set(FilePathKey_ProjectLogPath, (projectPath / "user" / "log").Native());
}
+#endif // AZ_TRAIT_OS_IS_HOST_OS_PLATFORM
+}
void MergeSettingsToRegistry_TargetBuildDependencyRegistry(SettingsRegistryInterface& registry, const AZStd::string_view platform,
const SettingsRegistryInterface::Specializations& specializations, AZStd::vector* scratchBuffer)
diff --git a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.h b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.h
index daa64c0343..56eec91813 100644
--- a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.h
+++ b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.h
@@ -87,9 +87,9 @@ namespace AZ::SettingsRegistryMergeUtils
AZ::IO::FixedMaxPath FindEngineRoot(SettingsRegistryInterface& settingsRegistry);
//! The algorithm that is used to find the project root is as follows
- //! 1. The first time this function is it performs a upward scan for a project.json file from
- //! the executable directory and if found stores that path to an internal key.
- //! In the same step it injects the path into the front of list of command line parameters
+ //! 1. The first time this function runs it performs an upward scan for a "project.json" file from
+ //! the executable directory and stores that path into an internal key.
+ //! In the same step it injects the path into the back of the command line parameters
//! using the --regset="{BootstrapSettingsRootKey}/project_path=" value
//! 2. Next the "{BootstrapSettingsRootKey}/project_path" is checked to see if it has a project path set
//!
diff --git a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryScriptUtils.cpp b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryScriptUtils.cpp
index c32591874b..5cd36785dc 100644
--- a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryScriptUtils.cpp
+++ b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryScriptUtils.cpp
@@ -15,20 +15,20 @@
namespace AZ::SettingsRegistryScriptUtils::Internal
{
- static void RegisterScriptProxyForNotify(SettingsRegistryScriptProxy& settingsRegistryProxy)
+ static void RegisterScriptProxyForNotify(SettingsRegistryInterface* settingsRegistry,
+ SettingsRegistryScriptProxy::NotifyEventProxy* notifyEventProxy)
{
- if (settingsRegistryProxy.IsValid())
+ if (settingsRegistry != nullptr)
{
- auto ForwardSettingsUpdateToProxyEvent = [&settingsRegistryProxy](AZStd::string_view path, AZ::SettingsRegistryInterface::Type)
+ auto ForwardSettingsUpdateToProxyEvent = [notifyEventProxy](AZStd::string_view path, AZ::SettingsRegistryInterface::Type)
{
- if (settingsRegistryProxy.m_notifyEventProxy)
+ if (notifyEventProxy)
{
- settingsRegistryProxy.m_notifyEventProxy->m_scriptNotifyEvent.Signal(path);
+ notifyEventProxy->m_scriptNotifyEvent.Signal(path);
}
};
// Register the forwarding function with the BehaviorContext
- settingsRegistryProxy.m_notifyEventProxy->m_settingsUpdatedHandler =
- settingsRegistryProxy.m_settingsRegistry->RegisterNotifier(ForwardSettingsUpdateToProxyEvent);
+ notifyEventProxy->m_settingsUpdatedHandler = settingsRegistry->RegisterNotifier(ForwardSettingsUpdateToProxyEvent);
}
}
@@ -37,7 +37,7 @@ namespace AZ::SettingsRegistryScriptUtils::Internal
: m_settingsRegistry(AZStd::move(settingsRegistry))
, m_notifyEventProxy(AZStd::make_shared())
{
- RegisterScriptProxyForNotify(*this);
+ RegisterScriptProxyForNotify(m_settingsRegistry.get(), m_notifyEventProxy.get());
}
// Raw AZ::SettingsRegistryInterface pointer is not owned by the proxy, so it's deleter is a no-op
@@ -45,7 +45,7 @@ namespace AZ::SettingsRegistryScriptUtils::Internal
: m_settingsRegistry(settingsRegistry, [](AZ::SettingsRegistryInterface*) {})
, m_notifyEventProxy(AZStd::make_shared())
{
- RegisterScriptProxyForNotify(*this);
+ RegisterScriptProxyForNotify(m_settingsRegistry.get(), m_notifyEventProxy.get());
}
// SettingsRegistryScriptProxy function that determines if the SettingsRegistry object is valid
diff --git a/Code/Framework/AzCore/AzCore/azcore_files.cmake b/Code/Framework/AzCore/AzCore/azcore_files.cmake
index 41229429f2..0c5a360844 100644
--- a/Code/Framework/AzCore/AzCore/azcore_files.cmake
+++ b/Code/Framework/AzCore/AzCore/azcore_files.cmake
@@ -41,6 +41,8 @@ set(FILES
Component/ComponentApplication.cpp
Component/ComponentApplication.h
Component/ComponentApplicationBus.h
+ Component/ComponentApplicationLifecycle.cpp
+ Component/ComponentApplicationLifecycle.h
Component/ComponentBus.cpp
Component/ComponentBus.h
Component/ComponentExport.h
diff --git a/Code/Framework/AzFramework/AzFramework/Application/Application.cpp b/Code/Framework/AzFramework/AzFramework/Application/Application.cpp
index f8d0ea8bb1..323e834413 100644
--- a/Code/Framework/AzFramework/AzFramework/Application/Application.cpp
+++ b/Code/Framework/AzFramework/AzFramework/Application/Application.cpp
@@ -10,6 +10,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -120,6 +121,11 @@ namespace AzFramework
m_archiveFileIO = AZStd::make_unique(m_archive.get());
AZ::IO::FileIOBase::SetInstance(m_archiveFileIO.get());
SetFileIOAliases();
+ // The FileIOAvailable event needs to be registered here as this event is sent out
+ // before the settings registry has merged the .setreg files from the
+ // (That happens in MergeSettingsToRegistry
+ AZ::ComponentApplicationLifecycle::RegisterEvent(*m_settingsRegistry, "FileIOAvailable");
+ AZ::ComponentApplicationLifecycle::SignalEvent(*m_settingsRegistry, "FileIOAvailable", R"({})");
}
if (auto nativeUI = AZ::Interface::Get(); nativeUI == nullptr)
@@ -172,6 +178,8 @@ namespace AzFramework
// Archive classes relies on the FileIOBase DirectInstance to close
// files properly
m_directFileIO.reset();
+
+ AZ::ComponentApplicationLifecycle::SignalEvent(*m_settingsRegistry, "FileIOUnavailable", R"({})");
}
void Application::Start(const Descriptor& descriptor, const StartupParameters& startupParameters)
@@ -196,7 +204,24 @@ namespace AzFramework
systemEntity->Activate();
AZ_Assert(systemEntity->GetState() == AZ::Entity::State::Active, "System Entity failed to activate.");
- m_isStarted = (systemEntity->GetState() == AZ::Entity::State::Active);
+ if (m_isStarted = (systemEntity->GetState() == AZ::Entity::State::Active); m_isStarted)
+ {
+ if (m_startupParameters.m_loadAssetCatalog)
+ {
+ // Start Monitoring Asset changes over the network and load the AssetCatalog
+ auto StartMonitoringAssetsAndLoadCatalog = [this](AZ::Data::AssetCatalogRequests* assetCatalogRequests)
+ {
+ if (AZ::IO::FixedMaxPath assetCatalogPath;
+ m_settingsRegistry->Get(assetCatalogPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_CacheRootFolder))
+ {
+ assetCatalogPath /= "assetcatalog.xml";
+ assetCatalogRequests->LoadCatalog(assetCatalogPath.c_str());
+ }
+ };
+ using AssetCatalogBus = AZ::Data::AssetCatalogRequestBus;
+ AssetCatalogBus::Broadcast(AZStd::move(StartMonitoringAssetsAndLoadCatalog));
+ }
+ }
}
void Application::PreModuleLoad()
@@ -210,6 +235,17 @@ namespace AzFramework
{
if (m_isStarted)
{
+ if (m_startupParameters.m_loadAssetCatalog)
+ {
+ // Stop Monitoring Assets changes
+ auto StopMonitoringAssets = [](AZ::Data::AssetCatalogRequests* assetCatalogRequests)
+ {
+ assetCatalogRequests->StopMonitoringAssets();
+ };
+ using AssetCatalogBus = AZ::Data::AssetCatalogRequestBus;
+ AssetCatalogBus::Broadcast(AZStd::move(StopMonitoringAssets));
+ }
+
ApplicationLifecycleEvents::Bus::Broadcast(&ApplicationLifecycleEvents::OnApplicationAboutToStop);
m_pimpl.reset();
diff --git a/Code/Framework/AzFramework/AzFramework/Archive/Archive.cpp b/Code/Framework/AzFramework/AzFramework/Archive/Archive.cpp
index cd30b753b5..c1c5775958 100644
--- a/Code/Framework/AzFramework/AzFramework/Archive/Archive.cpp
+++ b/Code/Framework/AzFramework/AzFramework/Archive/Archive.cpp
@@ -12,6 +12,7 @@
#include
#include
+#include
#include
#include
#include
@@ -363,6 +364,23 @@ namespace AZ::IO
, m_mainThreadId{ AZStd::this_thread::get_id() }
{
CompressionBus::Handler::BusConnect();
+
+ // If the settings registry is not available at this point,
+ // then something catastrophic has happened in the application startup.
+ // That should have been caught and messaged out earlier in startup.
+ if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
+ {
+ // Automatically register the event if it's not registered, because
+ // this system is initialized before the settings registry has loaded the event list.
+ AZ::ComponentApplicationLifecycle::RegisterHandler(
+ *settingsRegistry, m_componentApplicationLifecycleHandler,
+ [this](AZStd::string_view /*path*/, AZ::SettingsRegistryInterface::Type /*type*/)
+ {
+ OnSystemEntityActivated();
+ },
+ "SystemComponentsActivated",
+ /*autoRegisterEvent*/ true);
+ }
}
//////////////////////////////////////////////////////////////////////////
@@ -1175,13 +1193,20 @@ namespace AZ::IO
}
}
- auto bundleManifest = GetBundleManifest(desc.pZip);
AZStd::shared_ptr bundleCatalog;
+ auto bundleManifest = GetBundleManifest(desc.pZip);
if (bundleManifest)
{
bundleCatalog = GetBundleCatalog(desc.pZip, bundleManifest->GetCatalogName());
}
+ // If this archive is loaded before the serialize context is available, then the manifest and catalog will need to be loaded later.
+ if (!bundleManifest || !bundleCatalog)
+ {
+ m_archivesWithCatalogsToLoad.push_back(
+ ArchivesWithCatalogsToLoad(szFullPath, szBindRoot, flags, nextBundle, desc.m_strFileName));
+ }
+
bool usePrefabSystemForLevels = false;
AzFramework::ApplicationRequests::Bus::BroadcastResult(
usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemForLevelsEnabled);
@@ -1219,12 +1244,17 @@ namespace AZ::IO
m_levelOpenEvent.Signal(levelDirs);
}
- AZ::IO::ArchiveNotificationBus::Broadcast([](AZ::IO::ArchiveNotifications* archiveNotifications, const char* bundleName,
- AZStd::shared_ptr bundleManifest, const AZ::IO::FixedMaxPath& nextBundle, AZStd::shared_ptr bundleCatalog)
+ if (bundleManifest && bundleCatalog)
{
- archiveNotifications->BundleOpened(bundleName, bundleManifest, nextBundle.c_str(), bundleCatalog);
- }, desc.m_strFileName.c_str(), bundleManifest, nextBundle, bundleCatalog);
-
+ AZ::IO::ArchiveNotificationBus::Broadcast(
+ [](AZ::IO::ArchiveNotifications* archiveNotifications, const char* bundleName,
+ AZStd::shared_ptr bundleManifest, const AZ::IO::FixedMaxPath& nextBundle,
+ AZStd::shared_ptr bundleCatalog)
+ {
+ archiveNotifications->BundleOpened(bundleName, bundleManifest, nextBundle.c_str(), bundleCatalog);
+ },
+ desc.m_strFileName.c_str(), bundleManifest, nextBundle, bundleCatalog);
+ }
return true;
}
@@ -2138,7 +2168,7 @@ namespace AZ::IO
}
currentDirPattern = currentDir + AZ_FILESYSTEM_SEPARATOR_WILDCARD;
- currentFilePattern = currentDir + AZ_CORRECT_FILESYSTEM_SEPARATOR_STRING + "levels.pak";
+ currentFilePattern = currentDir + AZ_CORRECT_FILESYSTEM_SEPARATOR_STRING + "level.pak";
ZipDir::FileEntry* fileEntry = findFile.FindExact(currentFilePattern.c_str());
if (fileEntry)
@@ -2175,4 +2205,36 @@ namespace AZ::IO
return catalogInfo;
}
+
+ void Archive::OnSystemEntityActivated()
+ {
+ for (const auto& archiveInfo : m_archivesWithCatalogsToLoad)
+ {
+ AZStd::intrusive_ptr archive =
+ OpenArchive(archiveInfo.m_fullPath, archiveInfo.m_bindRoot, archiveInfo.m_flags, nullptr);
+ if (!archive)
+ {
+ continue;
+ }
+
+ ZipDir::CachePtr pZip = static_cast(archive.get())->GetCache();
+
+ AZStd::shared_ptr bundleCatalog;
+ auto bundleManifest = GetBundleManifest(pZip);
+ if (bundleManifest)
+ {
+ bundleCatalog = GetBundleCatalog(pZip, bundleManifest->GetCatalogName());
+ }
+
+ AZ::IO::ArchiveNotificationBus::Broadcast(
+ [](AZ::IO::ArchiveNotifications* archiveNotifications, const char* bundleName,
+ AZStd::shared_ptr bundleManifest, const AZ::IO::FixedMaxPath& nextBundle,
+ AZStd::shared_ptr bundleCatalog)
+ {
+ archiveNotifications->BundleOpened(bundleName, bundleManifest, nextBundle.c_str(), bundleCatalog);
+ },
+ archiveInfo.m_strFileName.c_str(), bundleManifest, archiveInfo.m_nextBundle, bundleCatalog);
+ }
+ m_archivesWithCatalogsToLoad.clear();
+ }
}
diff --git a/Code/Framework/AzFramework/AzFramework/Archive/Archive.h b/Code/Framework/AzFramework/AzFramework/Archive/Archive.h
index f08d90a66e..279702b433 100644
--- a/Code/Framework/AzFramework/AzFramework/Archive/Archive.h
+++ b/Code/Framework/AzFramework/AzFramework/Archive/Archive.h
@@ -19,6 +19,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -271,6 +272,11 @@ namespace AZ::IO
ZipDir::CachePtr* pZip = {}) const;
private:
+ // Archives can't be fully mounted until the system entity has been activated,
+ // because mounting them requires the BundlingSystemComponent and the serialization system
+ // to both be available.
+ void OnSystemEntityActivated();
+
bool OpenPackCommon(AZStd::string_view szBindRoot, AZStd::string_view pName, AZStd::intrusive_ptr pData = nullptr, bool addLevels = true);
bool OpenPacksCommon(AZStd::string_view szDir, AZStd::string_view pWildcardIn, AZStd::vector* pFullPaths = nullptr, bool addLevels = true);
@@ -313,6 +319,8 @@ namespace AZ::IO
mutable AZStd::shared_mutex m_csZips;
ZipArray m_arrZips;
+ AZ::SettingsRegistryInterface::NotifyEventHandler m_componentApplicationLifecycleHandler;
+
//////////////////////////////////////////////////////////////////////////
// Opened files collector.
//////////////////////////////////////////////////////////////////////////
@@ -339,5 +347,34 @@ namespace AZ::IO
// [LYN-2376] Remove once legacy slice support is removed
LevelPackOpenEvent m_levelOpenEvent;
LevelPackCloseEvent m_levelCloseEvent;
+
+ // If pak files are loaded before the serialization and bundling system
+ // are ready to go, their asset catalogs can't be loaded.
+ // In this case, cache information about those archives,
+ // and attempt to load the catalogs later, when the required systems are enabled.
+ struct ArchivesWithCatalogsToLoad
+ {
+ ArchivesWithCatalogsToLoad(
+ AZStd::string_view fullPath,
+ AZStd::string_view bindRoot,
+ int flags,
+ AZ::IO::PathView nextBundle,
+ AZ::IO::Path strFileName)
+ : m_fullPath(fullPath)
+ , m_bindRoot(bindRoot)
+ , m_flags(flags)
+ , m_nextBundle(nextBundle)
+ , m_strFileName(strFileName)
+ {
+ }
+
+ AZ::IO::Path m_strFileName;
+ AZStd::string m_fullPath;
+ AZStd::string m_bindRoot;
+ AZ::IO::PathView m_nextBundle;
+ int m_flags;
+ };
+
+ AZStd::vector m_archivesWithCatalogsToLoad;
};
}
diff --git a/Code/Framework/AzFramework/AzFramework/Asset/AssetCatalog.cpp b/Code/Framework/AzFramework/AzFramework/Asset/AssetCatalog.cpp
index e6b8211c28..3d76bcefc4 100644
--- a/Code/Framework/AzFramework/AzFramework/Asset/AssetCatalog.cpp
+++ b/Code/Framework/AzFramework/AzFramework/Asset/AssetCatalog.cpp
@@ -565,7 +565,7 @@ namespace AzFramework
if (!bytes.empty())
{
- AZStd::shared_ptr < AzFramework::AssetRegistry> prevRegistry;
+ AZStd::shared_ptr prevRegistry;
if (!m_initialized)
{
// First time initialization may have updates already processed which we want to apply
@@ -589,7 +589,6 @@ namespace AzFramework
AZ_TracePrintf("AssetCatalog", "Loaded registry containing %u assets.\n", m_registry->m_assetIdToInfo.size());
// It's currently possible in tools for us to have received updates from AP which were applied before the catalog was ready to load
- // due to CryPak and CrySystem coming online later than our components
if (!m_initialized)
{
ApplyDeltaCatalog(prevRegistry);
@@ -611,12 +610,13 @@ namespace AzFramework
// the mutex. If the listener tries to perform a blocking asset load via GetAsset() / BlockUntilLoadComplete(), the spawned asset
// thread will make a call to the AssetCatalogRequestBus and block on the held mutex. This would cause a deadlock, since the listener
// won't free the mutex until the load is complete.
- // So instead, queue the notification until the next tick, so that it doesn't occur within the AssetCatalogRequestBus mutex, and also
+ // So instead, queue the notification until after the AssetCatalogRequestBus mutex is unlocked for the current thread, and also
// so that the entire AssetCatalog initialization is complete.
- AZ::TickBus::QueueFunction([catalogRegistryString = AZStd::string(catalogRegistryFile)]()
- {
- AssetCatalogEventBus::Broadcast(&AssetCatalogEventBus::Events::OnCatalogLoaded, catalogRegistryString.c_str());
- });
+ auto OnCatalogLoaded = [catalogRegistryString = AZStd::string(catalogRegistryFile)]()
+ {
+ AssetCatalogEventBus::Broadcast(&AssetCatalogEventBus::Events::OnCatalogLoaded, catalogRegistryString.c_str());
+ };
+ AZ::Data::AssetCatalogRequestBus::QueueFunction(AZStd::move(OnCatalogLoaded));
}
}
@@ -978,6 +978,7 @@ namespace AzFramework
AZStd::lock_guard lock(m_registryMutex);
m_registry->Clear();
+ m_initialized = false;
}
diff --git a/Code/Framework/AzFramework/AzFramework/Asset/AssetRegistry.cpp b/Code/Framework/AzFramework/AzFramework/Asset/AssetRegistry.cpp
index f9eefa7639..26a22f4625 100644
--- a/Code/Framework/AzFramework/AzFramework/Asset/AssetRegistry.cpp
+++ b/Code/Framework/AzFramework/AzFramework/Asset/AssetRegistry.cpp
@@ -61,6 +61,7 @@ namespace AzFramework
//=========================================================================
void AssetRegistry::Clear()
{
+ m_assetDependencies = {};
m_assetIdToInfo = AssetIdToInfoMap();
m_assetPathToId = AssetPathToIdMap();
}
diff --git a/Code/Framework/AzFramework/AzFramework/FileTag/FileTag.cpp b/Code/Framework/AzFramework/AzFramework/FileTag/FileTag.cpp
index f820ee56ed..abbb9ec2f1 100644
--- a/Code/Framework/AzFramework/AzFramework/FileTag/FileTag.cpp
+++ b/Code/Framework/AzFramework/AzFramework/FileTag/FileTag.cpp
@@ -10,11 +10,11 @@
#include
#include
#include
+#include
#include
#include
#include
#include
-#include
#include
#include
#include
@@ -89,19 +89,19 @@ namespace AzFramework
bool FileTagManager::Save(FileTagType fileTagType, const AZStd::string& destinationFilePath = AZStd::string())
{
AzFramework::FileTag::FileTagAsset* fileTagAsset = GetFileTagAsset(fileTagType);
- AZStd::string filePathToSave = destinationFilePath;
+ AZ::IO::Path filePathToSave = destinationFilePath;
if (filePathToSave.empty())
{
filePathToSave = FileTagQueryManager::GetDefaultFileTagFilePath(fileTagType);
}
- if (!AzFramework::StringFunc::EndsWith(filePathToSave, AzFramework::FileTag::FileTagAsset::Extension()))
+ if (!filePathToSave.Extension().Native().ends_with(AzFramework::FileTag::FileTagAsset::Extension()))
{
AZ_Error("FileTag", false, "Unable to save tag file (%s). Invalid file extension, file tag can only have (%s) extension.\n", filePathToSave.c_str(), AzFramework::FileTag::FileTagAsset::Extension());
return false;
}
- return AZ::Utils::SaveObjectToFile(filePathToSave, AZ::DataStream::StreamType::ST_XML, fileTagAsset);
+ return AZ::Utils::SaveObjectToFile(filePathToSave.Native(), AZ::DataStream::StreamType::ST_XML, fileTagAsset);
}
AZ::Outcome FileTagManager::AddTagsInternal(AZStd::string filePath, FileTagType fileTagType, AZStd::vector fileTags, AzFramework::FileTag::FilePatternType filePatternType)
@@ -239,17 +239,22 @@ namespace AzFramework
QueryFileTagsEventBus::Handler::BusDisconnect();
}
- AZStd::string FileTagQueryManager::GetDefaultFileTagFilePath(FileTagType fileTagType)
+ AZ::IO::Path FileTagQueryManager::GetDefaultFileTagFilePath(FileTagType fileTagType)
{
- auto destinationFilePath = AZ::IO::FixedMaxPath(AZ::Utils::GetEnginePath()) / EngineAssetSourceRelPath;
+ AZ::IO::Path destinationFilePath;
+ if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
+ {
+ settingsRegistry->Get(destinationFilePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder);
+ }
+ destinationFilePath /= EngineAssetSourceRelPath;
destinationFilePath /= fileTagType == FileTagType::Exclude ? ExcludeFileName : IncludeFileName;
destinationFilePath.ReplaceExtension(AzFramework::FileTag::FileTagAsset::Extension());
- return destinationFilePath.String();
+ return destinationFilePath;
}
bool FileTagQueryManager::Load(const AZStd::string& filePath)
{
- AZStd::string fileToLoad = filePath;
+ AZ::IO::Path fileToLoad = filePath;
if (fileToLoad.empty())
{
fileToLoad = GetDefaultFileTagFilePath(m_fileTagType);
diff --git a/Code/Framework/AzFramework/AzFramework/FileTag/FileTag.h b/Code/Framework/AzFramework/AzFramework/FileTag/FileTag.h
index be3d6e6d08..d2dee3f629 100644
--- a/Code/Framework/AzFramework/AzFramework/FileTag/FileTag.h
+++ b/Code/Framework/AzFramework/AzFramework/FileTag/FileTag.h
@@ -11,6 +11,7 @@
#include
#include
#include
+#include
#include
namespace AzFramework
@@ -88,7 +89,7 @@ namespace AzFramework
/////////////////////////////////////////////////////////////////////////
- static AZStd::string GetDefaultFileTagFilePath(FileTagType fileTagType);
+ static AZ::IO::Path GetDefaultFileTagFilePath(FileTagType fileTagType);
protected:
diff --git a/Code/Framework/AzFramework/AzFramework/FileTag/FileTagComponent.cpp b/Code/Framework/AzFramework/AzFramework/FileTag/FileTagComponent.cpp
index 1e03a6df0d..1d09500415 100644
--- a/Code/Framework/AzFramework/AzFramework/FileTag/FileTagComponent.cpp
+++ b/Code/Framework/AzFramework/AzFramework/FileTag/FileTagComponent.cpp
@@ -16,6 +16,7 @@
#include
#include
+#include
#include
namespace AzFramework
@@ -66,7 +67,8 @@ namespace AzFramework
m_excludeFileQueryManager.reset(aznew FileTagQueryManager(FileTagType::Exclude));
if (!m_excludeFileQueryManager.get()->Load())
{
- AZ_Error("FileTagQueryComponent", false, "Not able to load default exclude file (%s). Please make sure that it exists on disk.\n", FileTagQueryManager::GetDefaultFileTagFilePath(FileTagType::Exclude).c_str());
+ AZ_Error("FileTagQueryComponent", false, "Not able to load default exclude file (%s). Please make sure that it exists on disk.\n",
+ FileTagQueryManager::GetDefaultFileTagFilePath(FileTagType::Exclude).c_str());
}
AzFramework::AssetCatalogEventBus::Handler::BusConnect();
diff --git a/Code/Framework/AzFramework/AzFramework/Viewport/ViewportBus.h b/Code/Framework/AzFramework/AzFramework/Viewport/ViewportBus.h
index 444173f773..131ecc0c57 100644
--- a/Code/Framework/AzFramework/AzFramework/Viewport/ViewportBus.h
+++ b/Code/Framework/AzFramework/AzFramework/Viewport/ViewportBus.h
@@ -8,8 +8,9 @@
#pragma once
-#include
#include
+#include
+#include
namespace AZ
{
@@ -20,18 +21,15 @@ namespace AZ
namespace AzFramework
{
- class ViewportRequests
- : public AZ::EBusTraits
+ class ViewportRequests : public AZ::EBusTraits
{
public:
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
- static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
+ static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
using BusIdType = ViewportId;
static void Reflect(AZ::ReflectContext* context);
- virtual ~ViewportRequests() {}
-
//! Gets the current camera's world to view matrix.
virtual const AZ::Matrix4x4& GetCameraViewMatrix() const = 0;
//! Sets the current camera's world to view matrix.
@@ -44,8 +42,36 @@ namespace AzFramework
virtual AZ::Transform GetCameraTransform() const = 0;
//! Convenience method, sets the camera's world to view matrix from this AZ::Transform.
virtual void SetCameraTransform(const AZ::Transform& transform) = 0;
+
+ protected:
+ ~ViewportRequests() = default;
};
using ViewportRequestBus = AZ::EBus;
-} //namespace AzFramework
+ //! The additional padding around the viewport when a viewport border is active.
+ struct ViewportBorderPadding
+ {
+ float m_top;
+ float m_bottom;
+ float m_left;
+ float m_right;
+ };
+
+ //! For performing queries about the state of the viewport border.
+ class ViewportBorderRequests : public AZ::EBusTraits
+ {
+ public:
+ static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
+ static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
+ using BusIdType = ViewportId;
+
+ //! Returns if a viewport border is in effect and what the current dimensions (padding) of the border are.
+ virtual AZStd::optional GetViewportBorderPadding() const = 0;
+
+ protected:
+ ~ViewportBorderRequests() = default;
+ };
+
+ using ViewportBorderRequestBus = AZ::EBus;
+} // namespace AzFramework
diff --git a/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbApplication.cpp b/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbApplication.cpp
index f57f4a89ac..780e1e72fe 100644
--- a/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbApplication.cpp
+++ b/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbApplication.cpp
@@ -10,6 +10,8 @@
#include
#include
+#include
+
namespace AzFramework
{
////////////////////////////////////////////////////////////////////////////////////////////////
@@ -34,6 +36,31 @@ namespace AzFramework
return m_xcbConnection.get();
}
+ void SetEnableXInput(xcb_connection_t* connection, bool enable) override
+ {
+ struct Mask
+ {
+ xcb_input_event_mask_t head;
+ xcb_input_xi_event_mask_t mask;
+ };
+ const Mask mask {
+ /*.head=*/{
+ /*.device_id=*/XCB_INPUT_DEVICE_ALL_MASTER,
+ /*.mask_len=*/1
+ },
+ /*.mask=*/ enable ?
+ (xcb_input_xi_event_mask_t)(XCB_INPUT_XI_EVENT_MASK_RAW_MOTION | XCB_INPUT_XI_EVENT_MASK_RAW_BUTTON_PRESS | XCB_INPUT_XI_EVENT_MASK_RAW_BUTTON_RELEASE) :
+ (xcb_input_xi_event_mask_t)XCB_NONE
+ };
+
+ const xcb_setup_t* xcbSetup = xcb_get_setup(connection);
+ const xcb_screen_t* xcbScreen = xcb_setup_roots_iterator(xcbSetup).data;
+
+ xcb_input_xi_select_events(connection, xcbScreen->root, 1, &mask.head);
+
+ xcb_flush(connection);
+ }
+
private:
XcbUniquePtr m_xcbConnection = nullptr;
};
diff --git a/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbConnectionManager.h b/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbConnectionManager.h
index daa5bf35af..ca7ce06e6c 100644
--- a/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbConnectionManager.h
+++ b/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbConnectionManager.h
@@ -24,6 +24,9 @@ namespace AzFramework
virtual ~XcbConnectionManager() = default;
virtual xcb_connection_t* GetXcbConnection() const = 0;
+
+ //! Enables/Disables XInput Raw Input events.
+ virtual void SetEnableXInput(xcb_connection_t* connection, bool enable) = 0;
};
class XcbConnectionManagerBusTraits
diff --git a/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbEventHandler.h b/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbEventHandler.h
index 251342093a..f32e45ed99 100644
--- a/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbEventHandler.h
+++ b/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbEventHandler.h
@@ -23,9 +23,6 @@ namespace AzFramework
virtual ~XcbEventHandler() = default;
virtual void HandleXcbEvent(xcb_generic_event_t* event) = 0;
-
- // ATTN This is used as a workaround for RAW Input events when using the Editor.
- virtual void PollSpecialEvents(){};
};
class XcbEventHandlerBusTraits : public AZ::EBusTraits
diff --git a/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbInputDeviceMouse.cpp b/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbInputDeviceMouse.cpp
index 56f21e6533..c3b7a97ccf 100644
--- a/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbInputDeviceMouse.cpp
+++ b/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbInputDeviceMouse.cpp
@@ -13,21 +13,68 @@
namespace AzFramework
{
- xcb_window_t GetSystemCursorFocusWindow()
+ xcb_window_t GetSystemCursorFocusWindow(xcb_connection_t* connection)
{
void* systemCursorFocusWindow = nullptr;
AzFramework::InputSystemCursorConstraintRequestBus::BroadcastResult(
systemCursorFocusWindow, &AzFramework::InputSystemCursorConstraintRequests::GetSystemCursorConstraintWindow);
- if (!systemCursorFocusWindow)
+ if (systemCursorFocusWindow)
{
- return XCB_NONE;
+ return static_cast(reinterpret_cast(systemCursorFocusWindow));
}
- // TODO Clang compile error because cast .... loses information. On GNU/Linux HWND is void* and on 64-bit
- // machines its obviously 64 bit but we receive the window id from m_renderOverlay.winId() which is xcb_window_t 32-bit.
+ // EWMH-compliant window managers set the "_NET_ACTIVE_WINDOW" property
+ // of the X server's root window to the currently active window. This
+ // retrieves value of that property.
- return static_cast(reinterpret_cast(systemCursorFocusWindow));
+ // Get the atom for the _NET_ACTIVE_WINDOW property
+ constexpr int propertyNameLength = 18;
+ xcb_generic_error_t* error = nullptr;
+ XcbStdFreePtr activeWindowAtom {xcb_intern_atom_reply(
+ connection,
+ xcb_intern_atom(connection, /*only_if_exists=*/ 1, propertyNameLength, "_NET_ACTIVE_WINDOW"),
+ &error
+ )};
+ if (!activeWindowAtom || error)
+ {
+ if (error)
+ {
+ AZ_Warning("XcbInput", false, "Retrieving _NET_ACTIVE_WINDOW atom failed : Error code %d", error->error_code);
+ free(error);
+ }
+ return XCB_WINDOW_NONE;
+ }
+
+ // Get the root window
+ const xcb_window_t rootWId = xcb_setup_roots_iterator(xcb_get_setup(connection)).data->root;
+
+ // Fetch the value of the root window's _NET_ACTIVE_WINDOW property
+ XcbStdFreePtr property {xcb_get_property_reply(
+ connection,
+ xcb_get_property(
+ /*c=*/connection,
+ /*_delete=*/ 0,
+ /*window=*/rootWId,
+ /*property=*/activeWindowAtom->atom,
+ /*type=*/XCB_ATOM_WINDOW,
+ /*long_offset=*/0,
+ /*long_length=*/1
+ ),
+ &error
+ )};
+
+ if (!property || error)
+ {
+ if (error)
+ {
+ AZ_Warning("XcbInput", false, "Retrieving _NET_ACTIVE_WINDOW atom failed : Error code %d", error->error_code);
+ free(error);
+ }
+ return XCB_WINDOW_NONE;
+ }
+
+ return *static_cast(xcb_get_property_value(property.get()));
}
xcb_connection_t* XcbInputDeviceMouse::s_xcbConnection = nullptr;
@@ -39,8 +86,7 @@ namespace AzFramework
: InputDeviceMouse::Implementation(inputDevice)
, m_systemCursorState(SystemCursorState::Unknown)
, m_systemCursorPositionNormalized(0.5f, 0.5f)
- , m_prevConstraintWindow(XCB_NONE)
- , m_focusWindow(XCB_NONE)
+ , m_focusWindow(XCB_WINDOW_NONE)
, m_cursorShown(true)
{
XcbEventHandlerBus::Handler::BusConnect();
@@ -57,14 +103,14 @@ namespace AzFramework
InputDeviceMouse::Implementation* XcbInputDeviceMouse::Create(InputDeviceMouse& inputDevice)
{
- auto* interface = AzFramework::XcbConnectionManagerInterface::Get();
+ const auto* interface = AzFramework::XcbConnectionManagerInterface::Get();
if (!interface)
{
AZ_Warning("XcbInput", false, "XCB interface not available");
return nullptr;
}
- s_xcbConnection = AzFramework::XcbConnectionManagerInterface::Get()->GetXcbConnection();
+ s_xcbConnection = interface->GetXcbConnection();
if (!s_xcbConnection)
{
AZ_Warning("XcbInput", false, "XCB connection not available");
@@ -126,7 +172,7 @@ namespace AzFramework
// Get window information.
const XcbStdFreePtr xcbGeometryReply{ xcb_get_geometry_reply(
- s_xcbConnection, xcb_get_geometry(s_xcbConnection, window), NULL) };
+ s_xcbConnection, xcb_get_geometry(s_xcbConnection, window), nullptr) };
if (!xcbGeometryReply)
{
@@ -137,7 +183,7 @@ namespace AzFramework
xcb_translate_coordinates(s_xcbConnection, window, s_xcbScreen->root, 0, 0);
const XcbStdFreePtr xkbTranslateCoordReply{ xcb_translate_coordinates_reply(
- s_xcbConnection, translate_coord, NULL) };
+ s_xcbConnection, translate_coord, nullptr) };
if (!xkbTranslateCoordReply)
{
@@ -173,11 +219,11 @@ namespace AzFramework
for (const auto& barrier : m_activeBarriers)
{
xcb_void_cookie_t cookie = xcb_xfixes_create_pointer_barrier_checked(
- s_xcbConnection, barrier.id, window, barrier.x0, barrier.y0, barrier.x1, barrier.y1, barrier.direction, 0, NULL);
- const XcbStdFreePtr xkbError{ xcb_request_check(s_xcbConnection, cookie) };
+ s_xcbConnection, barrier.id, window, barrier.x0, barrier.y0, barrier.x1, barrier.y1, barrier.direction, 0, nullptr);
+ const XcbStdFreePtr xcbError{ xcb_request_check(s_xcbConnection, cookie) };
AZ_Warning(
- "XcbInput", !xkbError, "XFixes, failed to create barrier %d at (%d %d %d %d)", barrier.id, barrier.x0, barrier.y0,
+ "XcbInput", !xcbError, "XFixes, failed to create barrier %d at (%d %d %d %d)", barrier.id, barrier.x0, barrier.y0,
barrier.x1, barrier.y1);
}
}
@@ -207,7 +253,7 @@ namespace AzFramework
const xcb_xfixes_query_version_cookie_t query_cookie = xcb_xfixes_query_version(s_xcbConnection, 5, 0);
- xcb_generic_error_t* error = NULL;
+ xcb_generic_error_t* error = nullptr;
const XcbStdFreePtr xkbQueryRequestReply{ xcb_xfixes_query_version_reply(
s_xcbConnection, query_cookie, &error) };
@@ -244,7 +290,7 @@ namespace AzFramework
const xcb_input_xi_query_version_cookie_t query_version_cookie = xcb_input_xi_query_version(s_xcbConnection, 2, 2);
- xcb_generic_error_t* error = NULL;
+ xcb_generic_error_t* error = nullptr;
const XcbStdFreePtr xkbQueryRequestReply{ xcb_input_xi_query_version_reply(
s_xcbConnection, query_version_cookie, &error) };
@@ -268,40 +314,13 @@ namespace AzFramework
return m_xInputInitialized;
}
- void XcbInputDeviceMouse::SetEnableXInput(bool enable)
- {
- struct
- {
- xcb_input_event_mask_t head;
- int mask;
- } mask;
-
- mask.head.deviceid = XCB_INPUT_DEVICE_ALL;
- mask.head.mask_len = 1;
-
- if (enable)
- {
- mask.mask = XCB_INPUT_XI_EVENT_MASK_RAW_MOTION | XCB_INPUT_XI_EVENT_MASK_RAW_BUTTON_PRESS |
- XCB_INPUT_XI_EVENT_MASK_RAW_BUTTON_RELEASE | XCB_INPUT_XI_EVENT_MASK_MOTION | XCB_INPUT_XI_EVENT_MASK_BUTTON_PRESS |
- XCB_INPUT_XI_EVENT_MASK_BUTTON_RELEASE;
- }
- else
- {
- mask.mask = XCB_NONE;
- }
-
- xcb_input_xi_select_events(s_xcbConnection, s_xcbScreen->root, 1, &mask.head);
-
- xcb_flush(s_xcbConnection);
- }
-
void XcbInputDeviceMouse::SetSystemCursorState(SystemCursorState systemCursorState)
{
if (systemCursorState != m_systemCursorState)
{
m_systemCursorState = systemCursorState;
- m_focusWindow = GetSystemCursorFocusWindow();
+ m_focusWindow = GetSystemCursorFocusWindow(s_xcbConnection);
HandleCursorState(m_focusWindow, systemCursorState);
}
@@ -309,52 +328,10 @@ namespace AzFramework
void XcbInputDeviceMouse::HandleCursorState(xcb_window_t window, SystemCursorState systemCursorState)
{
- bool confined = false, cursorShown = true;
- switch (systemCursorState)
- {
- case SystemCursorState::ConstrainedAndHidden:
- {
- //!< Constrained to the application's main window and hidden
- confined = true;
- cursorShown = false;
- }
- break;
- case SystemCursorState::ConstrainedAndVisible:
- {
- //!< Constrained to the application's main window and visible
- confined = true;
- }
- break;
- case SystemCursorState::UnconstrainedAndHidden:
- {
- //!< Free to move outside the main window but hidden while inside
- cursorShown = false;
- }
- break;
- case SystemCursorState::UnconstrainedAndVisible:
- {
- //!< Free to move outside the application's main window and visible
- }
- case SystemCursorState::Unknown:
- default:
- break;
- }
-
- // ATTN GetSystemCursorFocusWindow when getting out of the play in editor will return XCB_NONE
- // We need however the window id to reset the cursor.
- if (XCB_NONE == window && (confined || cursorShown))
- {
- // Reuse the previous window to reset states.
- window = m_prevConstraintWindow;
- m_prevConstraintWindow = XCB_NONE;
- }
- else
- {
- // Remember the window we used to modify cursor and barrier states.
- m_prevConstraintWindow = window;
- }
-
- SetEnableXInput(!cursorShown);
+ const bool confined = (systemCursorState == SystemCursorState::ConstrainedAndHidden) ||
+ (systemCursorState == SystemCursorState::ConstrainedAndVisible);
+ const bool cursorShown = (systemCursorState == SystemCursorState::ConstrainedAndVisible) ||
+ (systemCursorState == SystemCursorState::UnconstrainedAndVisible);
CreateBarriers(window, confined);
ShowCursor(window, cursorShown);
@@ -368,26 +345,26 @@ namespace AzFramework
void XcbInputDeviceMouse::SetSystemCursorPositionNormalizedInternal(xcb_window_t window, AZ::Vector2 positionNormalized)
{
// TODO Basically not done at all. Added only the basic functions needed.
- const XcbStdFreePtr xkbGeometryReply{ xcb_get_geometry_reply(
- s_xcbConnection, xcb_get_geometry(s_xcbConnection, window), NULL) };
+ const XcbStdFreePtr xcbGeometryReply{ xcb_get_geometry_reply(
+ s_xcbConnection, xcb_get_geometry(s_xcbConnection, window), nullptr) };
- if (!xkbGeometryReply)
+ if (!xcbGeometryReply)
{
return;
}
- const int16_t x = static_cast(positionNormalized.GetX() * xkbGeometryReply->width);
- const int16_t y = static_cast(positionNormalized.GetY() * xkbGeometryReply->height);
+ const int16_t x = static_cast(positionNormalized.GetX() * xcbGeometryReply->width);
+ const int16_t y = static_cast(positionNormalized.GetY() * xcbGeometryReply->height);
- xcb_warp_pointer(s_xcbConnection, XCB_NONE, window, 0, 0, 0, 0, x, y);
+ xcb_warp_pointer(s_xcbConnection, XCB_WINDOW_NONE, window, 0, 0, 0, 0, x, y);
xcb_flush(s_xcbConnection);
}
void XcbInputDeviceMouse::SetSystemCursorPositionNormalized(AZ::Vector2 positionNormalized)
{
- const xcb_window_t window = GetSystemCursorFocusWindow();
- if (XCB_NONE == window)
+ const xcb_window_t window = GetSystemCursorFocusWindow(s_xcbConnection);
+ if (XCB_WINDOW_NONE == window)
{
return;
}
@@ -401,7 +378,7 @@ namespace AzFramework
const xcb_query_pointer_cookie_t pointer = xcb_query_pointer(s_xcbConnection, window);
- const XcbStdFreePtr xkbQueryPointerReply{ xcb_query_pointer_reply(s_xcbConnection, pointer, NULL) };
+ const XcbStdFreePtr xkbQueryPointerReply{ xcb_query_pointer_reply(s_xcbConnection, pointer, nullptr) };
if (!xkbQueryPointerReply)
{
@@ -409,7 +386,7 @@ namespace AzFramework
}
const XcbStdFreePtr xkbGeometryReply{ xcb_get_geometry_reply(
- s_xcbConnection, xcb_get_geometry(s_xcbConnection, window), NULL) };
+ s_xcbConnection, xcb_get_geometry(s_xcbConnection, window), nullptr) };
if (!xkbGeometryReply)
{
@@ -429,8 +406,8 @@ namespace AzFramework
AZ::Vector2 XcbInputDeviceMouse::GetSystemCursorPositionNormalized() const
{
- const xcb_window_t window = GetSystemCursorFocusWindow();
- if (XCB_NONE == window)
+ const xcb_window_t window = GetSystemCursorFocusWindow(s_xcbConnection);
+ if (XCB_WINDOW_NONE == window)
{
return AZ::Vector2::CreateZero();
}
@@ -455,11 +432,11 @@ namespace AzFramework
cookie = xcb_xfixes_hide_cursor_checked(s_xcbConnection, window);
}
- const XcbStdFreePtr xkbError{ xcb_request_check(s_xcbConnection, cookie) };
+ const XcbStdFreePtr xcbError{ xcb_request_check(s_xcbConnection, cookie) };
- if (xkbError)
+ if (xcbError)
{
- AZ_Warning("XcbInput", false, "ShowCursor failed: %d", xkbError->error_code);
+ AZ_Warning("XcbInput", false, "ShowCursor failed: %d", xcbError->error_code);
return;
}
@@ -500,14 +477,6 @@ namespace AzFramework
}
}
- void XcbInputDeviceMouse::HandlePointerMotionEvents(const xcb_generic_event_t* event)
- {
- const xcb_input_motion_event_t* mouseMotionEvent = reinterpret_cast(event);
-
- m_systemCursorPosition[0] = mouseMotionEvent->event_x;
- m_systemCursorPosition[1] = mouseMotionEvent->event_y;
- }
-
void XcbInputDeviceMouse::HandleRawInputEvents(const xcb_ge_generic_event_t* event)
{
const xcb_ge_generic_event_t* genericEvent = reinterpret_cast(event);
@@ -552,78 +521,20 @@ namespace AzFramework
}
}
- void XcbInputDeviceMouse::PollSpecialEvents()
- {
- while (xcb_generic_event_t* genericEvent = xcb_poll_for_queued_event(s_xcbConnection))
- {
- // TODO Is the following correct? If we are showing the cursor, don't poll RAW Input events.
- switch (genericEvent->response_type & ~0x80)
- {
- case XCB_GE_GENERIC:
- {
- const xcb_ge_generic_event_t* geGenericEvent = reinterpret_cast(genericEvent);
-
- // Only handle raw inputs if we have focus.
- // Handle Raw Input events first.
- if ((geGenericEvent->event_type == XCB_INPUT_RAW_BUTTON_PRESS) ||
- (geGenericEvent->event_type == XCB_INPUT_RAW_BUTTON_RELEASE) ||
- (geGenericEvent->event_type == XCB_INPUT_RAW_MOTION))
- {
- HandleRawInputEvents(geGenericEvent);
-
- free(genericEvent);
- }
- }
- break;
- }
- }
- }
-
void XcbInputDeviceMouse::HandleXcbEvent(xcb_generic_event_t* event)
{
switch (event->response_type & ~0x80)
{
- // QT5 is using by default XInput which means we do need to check for XCB_GE_GENERIC event to parse all mouse related events.
+ // XInput raw events are sent from the server as a XCB_GE_GENERIC
+ // event. A XCB_GE_GENERIC event is typecast to a
+ // xcb_ge_generic_event_t, which is distinct from a
+ // xcb_generic_event_t, and exists so that X11 extensions can extend
+ // the event emission beyond the size that a normal X11 event could
+ // contain.
case XCB_GE_GENERIC:
{
const xcb_ge_generic_event_t* genericEvent = reinterpret_cast(event);
-
- // Handling RAW Inputs here works in GameMode but not in Editor mode because QT is
- // not handling RAW input events and passing to.
- if (!m_cursorShown)
- {
- // Handle Raw Input events first.
- if ((genericEvent->event_type == XCB_INPUT_RAW_BUTTON_PRESS) ||
- (genericEvent->event_type == XCB_INPUT_RAW_BUTTON_RELEASE) || (genericEvent->event_type == XCB_INPUT_RAW_MOTION))
- {
- HandleRawInputEvents(genericEvent);
- }
- }
- else
- {
- switch (genericEvent->event_type)
- {
- case XCB_INPUT_BUTTON_PRESS:
- {
- const xcb_input_button_press_event_t* mouseButtonEvent =
- reinterpret_cast(genericEvent);
- HandleButtonPressEvents(mouseButtonEvent->detail, true);
- }
- break;
- case XCB_INPUT_BUTTON_RELEASE:
- {
- const xcb_input_button_release_event_t* mouseButtonEvent =
- reinterpret_cast(genericEvent);
- HandleButtonPressEvents(mouseButtonEvent->detail, false);
- }
- break;
- case XCB_INPUT_MOTION:
- {
- HandlePointerMotionEvents(event);
- }
- break;
- }
- }
+ HandleRawInputEvents(genericEvent);
}
break;
case XCB_FOCUS_IN:
@@ -634,6 +545,9 @@ namespace AzFramework
m_focusWindow = focusInEvent->event;
HandleCursorState(m_focusWindow, m_systemCursorState);
}
+
+ auto* interface = AzFramework::XcbConnectionManagerInterface::Get();
+ interface->SetEnableXInput(interface->GetXcbConnection(), true);
}
break;
case XCB_FOCUS_OUT:
@@ -644,7 +558,10 @@ namespace AzFramework
ProcessRawEventQueues();
ResetInputChannelStates();
- m_focusWindow = XCB_NONE;
+ m_focusWindow = XCB_WINDOW_NONE;
+
+ auto* interface = AzFramework::XcbConnectionManagerInterface::Get();
+ interface->SetEnableXInput(interface->GetXcbConnection(), false);
}
break;
}
diff --git a/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbInputDeviceMouse.h b/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbInputDeviceMouse.h
index 106d204ca9..a69a8a9ec5 100644
--- a/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbInputDeviceMouse.h
+++ b/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbInputDeviceMouse.h
@@ -65,9 +65,6 @@ namespace AzFramework
//! \ref AzFramework::InputDeviceMouse::Implementation::TickInputDevice
void TickInputDevice() override;
- //! This method is called by the Editor to accommodate some events with the Editor. Never called in Game mode.
- void PollSpecialEvents() override;
-
//! Handle X11 events.
void HandleXcbEvent(xcb_generic_event_t* event) override;
@@ -77,9 +74,6 @@ namespace AzFramework
//! Initialize XInput extension. Used for raw input during confinement and showing/hiding the cursor.
static bool InitializeXInput();
- //! Enables/Disables XInput Raw Input events.
- void SetEnableXInput(bool enable);
-
//! Create barriers.
void CreateBarriers(xcb_window_t window, bool create);
@@ -98,9 +92,6 @@ namespace AzFramework
//! Handle button press/release events.
void HandleButtonPressEvents(uint32_t detail, bool pressed);
- //! Handle motion notify events.
- void HandlePointerMotionEvents(const xcb_generic_event_t* event);
-
//! Will set cursor states and confinement modes.
void HandleCursorState(xcb_window_t window, SystemCursorState systemCursorState);
@@ -160,7 +151,6 @@ namespace AzFramework
AZ::Vector2 m_cursorHiddenPosition;
AZ::Vector2 m_systemCursorPositionNormalized;
- uint32_t m_systemCursorPosition[MAX_XI_RAW_AXIS];
static xcb_connection_t* s_xcbConnection;
static xcb_screen_t* s_xcbScreen;
@@ -171,9 +161,6 @@ namespace AzFramework
//! Will be true if the xinput2 extension could be initialized.
static bool m_xInputInitialized;
- //! The window that had focus
- xcb_window_t m_prevConstraintWindow;
-
//! The current window that has focus
xcb_window_t m_focusWindow;
diff --git a/Code/Framework/AzFramework/Platform/Mac/AzFramework/Asset/AssetSystemComponentHelper_Mac.cpp b/Code/Framework/AzFramework/Platform/Mac/AzFramework/Asset/AssetSystemComponentHelper_Mac.cpp
index c6f481b65b..a3381dabb8 100644
--- a/Code/Framework/AzFramework/Platform/Mac/AzFramework/Asset/AssetSystemComponentHelper_Mac.cpp
+++ b/Code/Framework/AzFramework/Platform/Mac/AzFramework/Asset/AssetSystemComponentHelper_Mac.cpp
@@ -10,6 +10,8 @@
#include
#include
+#include
+
#include
#include
@@ -24,14 +26,20 @@ namespace AzFramework::AssetSystem::Platform
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 /= "../../../AssetProcessor.app/Contents/MacOS/AssetProcessor";
assetProcessorPath = assetProcessorPath.LexicallyNormal();
if (!AZ::IO::SystemFile::Exists(assetProcessorPath.c_str()))
{
- // Check for existence of one under a "bin" directory, i.e. engineRoot is an SDK structure.
- assetProcessorPath =
- AZ::IO::FixedMaxPath{engineRoot} / "bin" / AZ_TRAIT_OS_PLATFORM_NAME / AZ_BUILD_CONFIGURATION_TYPE / "AssetProcessor.app";
+ if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
+ {
+ if (AZ::IO::FixedMaxPath installedBinariesPath;
+ settingsRegistry->Get(installedBinariesPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_InstalledBinaryFolder))
+ {
+ // Check for existence of one under a "bin" directory, i.e. engineRoot is an SDK structure.
+ assetProcessorPath = AZ::IO::FixedMaxPath{ engineRoot } / installedBinariesPath / "AssetProcessor.app/Contents/MacOS/AssetProcessor";
+ }
+ }
if (!AZ::IO::SystemFile::Exists(assetProcessorPath.c_str()))
{
@@ -39,23 +47,21 @@ namespace AzFramework::AssetSystem::Platform
}
}
- auto fullLaunchCommand = AZ::IO::FixedMaxPathString::format(R"(open -g "%s" --args --start-hidden)", assetProcessorPath.c_str());
+ AZStd::string commandLineParams;
// Add the engine path to the launch command if not empty
if (!engineRoot.empty())
{
- fullLaunchCommand += R"( --engine-path=")";
- fullLaunchCommand += engineRoot;
- fullLaunchCommand += '"';
+ commandLineParams += AZStd::string::format("\"--engine-path=\"%s\"\"", engineRoot.data());
}
- // Add the active project path to the launch command if not empty
if (!projectPath.empty())
{
- fullLaunchCommand += R"( --project-path=")";
- fullLaunchCommand += projectPath;
- fullLaunchCommand += '"';
+ commandLineParams += AZStd::string::format(" \"--regset=/Amazon/AzCore/Bootstrap/project_path=\"%s\"\"", projectPath.data());
}
- return system(fullLaunchCommand.c_str()) == 0;
+ AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo;
+ processLaunchInfo.m_processExecutableString = AZStd::move(assetProcessorPath.Native());
+ processLaunchInfo.m_commandlineParameters = commandLineParams;
+ return AzFramework::ProcessLauncher::LaunchUnwatchedProcess(processLaunchInfo);
}
}
diff --git a/Code/Framework/AzFramework/Tests/ArchiveCompressionTests.cpp b/Code/Framework/AzFramework/Tests/ArchiveCompressionTests.cpp
index 6cde5b5e84..aa1a27f9b0 100644
--- a/Code/Framework/AzFramework/Tests/ArchiveCompressionTests.cpp
+++ b/Code/Framework/AzFramework/Tests/ArchiveCompressionTests.cpp
@@ -41,7 +41,9 @@ namespace UnitTest
auto projectPathKey =
AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path";
- registry->Set(projectPathKey, "AutomatedTesting");
+ AZ::IO::FixedMaxPath enginePath;
+ registry->Get(enginePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder);
+ registry->Set(projectPathKey, (enginePath / "AutomatedTesting").Native());
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry);
m_application->Start({});
diff --git a/Code/Framework/AzFramework/Tests/ArchiveTests.cpp b/Code/Framework/AzFramework/Tests/ArchiveTests.cpp
index 37babb49a8..ff0e3ab724 100644
--- a/Code/Framework/AzFramework/Tests/ArchiveTests.cpp
+++ b/Code/Framework/AzFramework/Tests/ArchiveTests.cpp
@@ -45,7 +45,9 @@ namespace UnitTest
auto projectPathKey =
AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path";
- registry->Set(projectPathKey, "AutomatedTesting");
+ AZ::IO::FixedMaxPath enginePath;
+ registry->Get(enginePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder);
+ registry->Set(projectPathKey, (enginePath / "AutomatedTesting").Native());
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry);
m_application->Start({});
diff --git a/Code/Framework/AzFramework/Tests/AssetCatalog.cpp b/Code/Framework/AzFramework/Tests/AssetCatalog.cpp
index 8cdc54f34f..8a24d164cd 100644
--- a/Code/Framework/AzFramework/Tests/AssetCatalog.cpp
+++ b/Code/Framework/AzFramework/Tests/AssetCatalog.cpp
@@ -305,10 +305,14 @@ namespace UnitTest
AZ::SettingsRegistryInterface* registry = AZ::SettingsRegistry::Get();
auto projectPathKey =
AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path";
- registry->Set(projectPathKey, "AutomatedTesting");
+ AZ::IO::FixedMaxPath enginePath;
+ registry->Get(enginePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder);
+ registry->Set(projectPathKey, (enginePath / "AutomatedTesting").Native());
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry);
- m_app->Start(desc);
+ AZ::ComponentApplication::StartupParameters startupParameters;
+ startupParameters.m_loadAssetCatalog = false;
+ m_app->Start(desc, startupParameters);
// Without this, the user settings component would attempt to save on finalize/shutdown. Since the file is
// shared across the whole engine, if multiple tests are run in parallel, the saving could cause a crash
diff --git a/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/Actions.h b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/Actions.h
index 1650ff4f8d..e86904efa3 100644
--- a/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/Actions.h
+++ b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/Actions.h
@@ -11,6 +11,13 @@
#include
#include
+ACTION_TEMPLATE(ReturnMalloc,
+ HAS_1_TEMPLATE_PARAMS(typename, T),
+ AND_0_VALUE_PARAMS()) {
+ T* value = static_cast(malloc(sizeof(T)));
+ *value = T{};
+ return value;
+}
ACTION_TEMPLATE(ReturnMalloc,
HAS_1_TEMPLATE_PARAMS(typename, T),
AND_1_VALUE_PARAMS(p0)) {
@@ -25,3 +32,38 @@ ACTION_TEMPLATE(ReturnMalloc,
*value = T{ p0, p1 };
return value;
}
+ACTION_TEMPLATE(ReturnMalloc,
+ HAS_1_TEMPLATE_PARAMS(typename, T),
+ AND_3_VALUE_PARAMS(p0, p1, p2)) {
+ T* value = static_cast(malloc(sizeof(T)));
+ *value = T{ p0, p1, p2 };
+ return value;
+}
+ACTION_TEMPLATE(ReturnMalloc,
+ HAS_1_TEMPLATE_PARAMS(typename, T),
+ AND_4_VALUE_PARAMS(p0, p1, p2, p3)) {
+ T* value = static_cast(malloc(sizeof(T)));
+ *value = T{ p0, p1, p2, p3 };
+ return value;
+}
+ACTION_TEMPLATE(ReturnMalloc,
+ HAS_1_TEMPLATE_PARAMS(typename, T),
+ AND_5_VALUE_PARAMS(p0, p1, p2, p3, p4)) {
+ T* value = static_cast(malloc(sizeof(T)));
+ *value = T{ p0, p1, p2, p3, p4 };
+ return value;
+}
+ACTION_TEMPLATE(ReturnMalloc,
+ HAS_1_TEMPLATE_PARAMS(typename, T),
+ AND_6_VALUE_PARAMS(p0, p1, p2, p3, p4, p5)) {
+ T* value = static_cast(malloc(sizeof(T)));
+ *value = T{ p0, p1, p2, p3, p4, p5 };
+ return value;
+}
+ACTION_TEMPLATE(ReturnMalloc,
+ HAS_1_TEMPLATE_PARAMS(typename, T),
+ AND_7_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6)) {
+ T* value = static_cast(malloc(sizeof(T)));
+ *value = T{ p0, p1, p2, p3, p4, p5, p6 };
+ return value;
+}
diff --git a/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/MockXcbInterface.cpp b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/MockXcbInterface.cpp
index b15809a4c6..a19642e388 100644
--- a/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/MockXcbInterface.cpp
+++ b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/MockXcbInterface.cpp
@@ -32,6 +32,82 @@ xcb_generic_error_t* xcb_request_check(xcb_connection_t* c, xcb_void_cookie_t co
{
return MockXcbInterface::Instance()->xcb_request_check(c, cookie);
}
+const xcb_setup_t* xcb_get_setup(xcb_connection_t *c)
+{
+ return MockXcbInterface::Instance()->xcb_get_setup(c);
+}
+xcb_screen_iterator_t xcb_setup_roots_iterator(const xcb_setup_t* R)
+{
+ return MockXcbInterface::Instance()->xcb_setup_roots_iterator(R);
+}
+const xcb_query_extension_reply_t* xcb_get_extension_data(xcb_connection_t* c, xcb_extension_t* ext)
+{
+ return MockXcbInterface::Instance()->xcb_get_extension_data(c, ext);
+}
+int xcb_flush(xcb_connection_t *c)
+{
+ return MockXcbInterface::Instance()->xcb_flush(c);
+}
+xcb_query_pointer_cookie_t xcb_query_pointer(xcb_connection_t* c, xcb_window_t window)
+{
+ return MockXcbInterface::Instance()->xcb_query_pointer(c, window);
+}
+xcb_query_pointer_reply_t* xcb_query_pointer_reply(xcb_connection_t* c, xcb_query_pointer_cookie_t cookie, xcb_generic_error_t** e)
+{
+ return MockXcbInterface::Instance()->xcb_query_pointer_reply(c, cookie, e);
+}
+xcb_get_geometry_cookie_t xcb_get_geometry(xcb_connection_t* c, xcb_drawable_t drawable)
+{
+ return MockXcbInterface::Instance()->xcb_get_geometry(c, drawable);
+}
+xcb_get_geometry_reply_t* xcb_get_geometry_reply(xcb_connection_t* c, xcb_get_geometry_cookie_t cookie, xcb_generic_error_t** e)
+{
+ return MockXcbInterface::Instance()->xcb_get_geometry_reply(c, cookie, e);
+}
+xcb_void_cookie_t xcb_warp_pointer(
+ xcb_connection_t* c,
+ xcb_window_t src_window,
+ xcb_window_t dst_window,
+ int16_t src_x,
+ int16_t src_y,
+ uint16_t src_width,
+ uint16_t src_height,
+ int16_t dst_x,
+ int16_t dst_y)
+{
+ return MockXcbInterface::Instance()->xcb_warp_pointer(c, src_window, dst_window, src_x, src_y, src_width, src_height, dst_x, dst_y);
+}
+xcb_intern_atom_cookie_t xcb_intern_atom(xcb_connection_t* c, uint8_t only_if_exists, uint16_t name_len, const char* name)
+{
+ return MockXcbInterface::Instance()->xcb_intern_atom(c, only_if_exists, name_len, name);
+}
+xcb_intern_atom_reply_t* xcb_intern_atom_reply(xcb_connection_t* c, xcb_intern_atom_cookie_t cookie, xcb_generic_error_t** e)
+{
+ return MockXcbInterface::Instance()->xcb_intern_atom_reply(c, cookie, e);
+}
+xcb_get_property_cookie_t xcb_get_property(
+ xcb_connection_t* c,
+ uint8_t _delete,
+ xcb_window_t window,
+ xcb_atom_t property,
+ xcb_atom_t type,
+ uint32_t long_offset,
+ uint32_t long_length)
+{
+ return MockXcbInterface::Instance()->xcb_get_property(c, _delete, window, property, type, long_offset, long_length);
+}
+xcb_get_property_reply_t* xcb_get_property_reply(xcb_connection_t* c, xcb_get_property_cookie_t cookie, xcb_generic_error_t** e)
+{
+ return MockXcbInterface::Instance()->xcb_get_property_reply(c, cookie, e);
+}
+void* xcb_get_property_value(const xcb_get_property_reply_t* R)
+{
+ return MockXcbInterface::Instance()->xcb_get_property_value(R);
+}
+uint32_t xcb_generate_id(xcb_connection_t *c)
+{
+ return MockXcbInterface::Instance()->xcb_generate_id(c);
+}
// ----------------------------------------------------------------------------
// xcb-xkb
@@ -116,4 +192,76 @@ xkb_state_component xkb_state_update_mask(
state, depressed_mods, latched_mods, locked_mods, depressed_layout, latched_layout, locked_layout);
}
+// ----------------------------------------------------------------------------
+// xcb-xfixes
+xcb_xfixes_query_version_cookie_t xcb_xfixes_query_version(
+ xcb_connection_t* c, uint32_t client_major_version, uint32_t client_minor_version)
+{
+ return MockXcbInterface::Instance()->xcb_xfixes_query_version(c, client_major_version, client_minor_version);
+}
+xcb_xfixes_query_version_reply_t* xcb_xfixes_query_version_reply(
+ xcb_connection_t* c, xcb_xfixes_query_version_cookie_t cookie, xcb_generic_error_t** e)
+{
+ return MockXcbInterface::Instance()->xcb_xfixes_query_version_reply(c, cookie, e);
+}
+xcb_void_cookie_t xcb_xfixes_show_cursor_checked(xcb_connection_t* c, xcb_window_t window)
+{
+ return MockXcbInterface::Instance()->xcb_xfixes_show_cursor_checked(c, window);
+}
+xcb_void_cookie_t xcb_xfixes_hide_cursor_checked(xcb_connection_t* c, xcb_window_t window)
+{
+ return MockXcbInterface::Instance()->xcb_xfixes_hide_cursor_checked(c, window);
+}
+xcb_void_cookie_t xcb_xfixes_delete_pointer_barrier_checked(xcb_connection_t* c, xcb_xfixes_barrier_t barrier)
+{
+ return MockXcbInterface::Instance()->xcb_xfixes_delete_pointer_barrier_checked(c, barrier);
+}
+xcb_translate_coordinates_cookie_t xcb_translate_coordinates(xcb_connection_t* c, xcb_window_t src_window, xcb_window_t dst_window, int16_t src_x, int16_t src_y)
+{
+ return MockXcbInterface::Instance()->xcb_translate_coordinates(c, src_window, dst_window, src_x, src_y);
+}
+xcb_translate_coordinates_reply_t* xcb_translate_coordinates_reply(xcb_connection_t* c, xcb_translate_coordinates_cookie_t cookie, xcb_generic_error_t** e)
+{
+ return MockXcbInterface::Instance()->xcb_translate_coordinates_reply(c, cookie, e);
+}
+xcb_void_cookie_t xcb_xfixes_create_pointer_barrier_checked(
+ xcb_connection_t* c,
+ xcb_xfixes_barrier_t barrier,
+ xcb_window_t window,
+ uint16_t x1,
+ uint16_t y1,
+ uint16_t x2,
+ uint16_t y2,
+ uint32_t directions,
+ uint16_t num_devices,
+ const uint16_t* devices)
+{
+ return MockXcbInterface::Instance()->xcb_xfixes_create_pointer_barrier_checked(c, barrier, window, x1, y1, x2, y2, directions, num_devices, devices);
+}
+
+// ----------------------------------------------------------------------------
+// xcb-xinput
+xcb_input_xi_query_version_cookie_t xcb_input_xi_query_version(xcb_connection_t* c, uint16_t major_version, uint16_t minor_version)
+{
+ return MockXcbInterface::Instance()->xcb_input_xi_query_version(c, major_version, minor_version);
+}
+xcb_input_xi_query_version_reply_t* xcb_input_xi_query_version_reply(
+ xcb_connection_t* c, xcb_input_xi_query_version_cookie_t cookie, xcb_generic_error_t** e)
+{
+ return MockXcbInterface::Instance()->xcb_input_xi_query_version_reply(c, cookie, e);
+}
+xcb_void_cookie_t xcb_input_xi_select_events(
+ xcb_connection_t* c, xcb_window_t window, uint16_t num_mask, const xcb_input_event_mask_t* masks)
+{
+ return MockXcbInterface::Instance()->xcb_input_xi_select_events(c, window, num_mask, masks);
+}
+int xcb_input_raw_button_press_axisvalues_length (const xcb_input_raw_button_press_event_t *R)
+{
+ return MockXcbInterface::Instance()->xcb_input_raw_button_press_axisvalues_length(R);
+}
+xcb_input_fp3232_t* xcb_input_raw_button_press_axisvalues_raw(const xcb_input_raw_button_press_event_t* R)
+{
+ return MockXcbInterface::Instance()->xcb_input_raw_button_press_axisvalues_raw(R);
+}
+
}
diff --git a/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/MockXcbInterface.h b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/MockXcbInterface.h
index b57751344e..c554993110 100644
--- a/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/MockXcbInterface.h
+++ b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/MockXcbInterface.h
@@ -18,6 +18,8 @@
#undef explicit
#include
#include
+#include
+#include
#include "Printers.h"
@@ -62,6 +64,37 @@ public:
MOCK_CONST_METHOD1(xcb_disconnect, void(xcb_connection_t* c));
MOCK_CONST_METHOD1(xcb_poll_for_event, xcb_generic_event_t*(xcb_connection_t* c));
MOCK_CONST_METHOD2(xcb_request_check, xcb_generic_error_t*(xcb_connection_t* c, xcb_void_cookie_t cookie));
+ MOCK_CONST_METHOD1(xcb_get_setup, const xcb_setup_t*(xcb_connection_t *c));
+ MOCK_CONST_METHOD1(xcb_setup_roots_iterator, xcb_screen_iterator_t(const xcb_setup_t* R));
+ MOCK_CONST_METHOD2(xcb_get_extension_data, const xcb_query_extension_reply_t*(xcb_connection_t* c, xcb_extension_t* ext));
+ MOCK_CONST_METHOD1(xcb_flush, int(xcb_connection_t *c));
+ MOCK_CONST_METHOD2(xcb_query_pointer, xcb_query_pointer_cookie_t(xcb_connection_t* c, xcb_window_t window));
+ MOCK_CONST_METHOD3(xcb_query_pointer_reply, xcb_query_pointer_reply_t*(xcb_connection_t* c, xcb_query_pointer_cookie_t cookie, xcb_generic_error_t** e));
+ MOCK_CONST_METHOD2(xcb_get_geometry, xcb_get_geometry_cookie_t(xcb_connection_t* c, xcb_drawable_t drawable));
+ MOCK_CONST_METHOD3(xcb_get_geometry_reply, xcb_get_geometry_reply_t*(xcb_connection_t* c, xcb_get_geometry_cookie_t cookie, xcb_generic_error_t** e));
+ MOCK_CONST_METHOD9(xcb_warp_pointer, xcb_void_cookie_t(
+ xcb_connection_t* c,
+ xcb_window_t src_window,
+ xcb_window_t dst_window,
+ int16_t src_x,
+ int16_t src_y,
+ uint16_t src_width,
+ uint16_t src_height,
+ int16_t dst_x,
+ int16_t dst_y));
+ MOCK_CONST_METHOD4(xcb_intern_atom, xcb_intern_atom_cookie_t(xcb_connection_t* c, uint8_t only_if_exists, uint16_t name_len, const char* name));
+ MOCK_CONST_METHOD3(xcb_intern_atom_reply, xcb_intern_atom_reply_t*(xcb_connection_t* c, xcb_intern_atom_cookie_t cookie, xcb_generic_error_t** e));
+ MOCK_CONST_METHOD7(xcb_get_property, xcb_get_property_cookie_t(
+ xcb_connection_t* c,
+ uint8_t _delete,
+ xcb_window_t window,
+ xcb_atom_t property,
+ xcb_atom_t type,
+ uint32_t long_offset,
+ uint32_t long_length));
+ MOCK_CONST_METHOD3(xcb_get_property_reply, xcb_get_property_reply_t*(xcb_connection_t* c, xcb_get_property_cookie_t cookie, xcb_generic_error_t** e));
+ MOCK_CONST_METHOD1(xcb_get_property_value, void*(const xcb_get_property_reply_t* R));
+ MOCK_CONST_METHOD1(xcb_generate_id, uint32_t(xcb_connection_t *c));
// xcb-xkb
MOCK_CONST_METHOD3(xcb_xkb_use_extension, xcb_xkb_use_extension_cookie_t(xcb_connection_t* c, uint16_t wantedMajor, uint16_t wantedMinor));
@@ -83,6 +116,33 @@ public:
MOCK_CONST_METHOD4(xkb_state_key_get_utf8, int(xkb_state* state, xkb_keycode_t key, char* buffer, size_t size));
MOCK_CONST_METHOD7(xkb_state_update_mask, xkb_state_component(xkb_state* state, xkb_mod_mask_t depressed_mods, xkb_mod_mask_t latched_mods, xkb_mod_mask_t locked_mods, xkb_layout_index_t depressed_layout, xkb_layout_index_t latched_layout, xkb_layout_index_t locked_layout));
+ // xcb-xfixes
+ MOCK_CONST_METHOD3(xcb_xfixes_query_version, xcb_xfixes_query_version_cookie_t(xcb_connection_t* c, uint32_t client_major_version, uint32_t client_minor_version));
+ MOCK_CONST_METHOD3(xcb_xfixes_query_version_reply, xcb_xfixes_query_version_reply_t*(xcb_connection_t* c, xcb_xfixes_query_version_cookie_t cookie, xcb_generic_error_t** e));
+ MOCK_CONST_METHOD2(xcb_xfixes_show_cursor_checked, xcb_void_cookie_t(xcb_connection_t* c, xcb_window_t window));
+ MOCK_CONST_METHOD2(xcb_xfixes_hide_cursor_checked, xcb_void_cookie_t(xcb_connection_t* c, xcb_window_t window));
+ MOCK_CONST_METHOD2(xcb_xfixes_delete_pointer_barrier_checked, xcb_void_cookie_t(xcb_connection_t* c, xcb_xfixes_barrier_t barrier));
+ MOCK_CONST_METHOD5(xcb_translate_coordinates, xcb_translate_coordinates_cookie_t(xcb_connection_t* c, xcb_window_t src_window, xcb_window_t dst_window, int16_t src_x, int16_t src_y));
+ MOCK_CONST_METHOD3(xcb_translate_coordinates_reply, xcb_translate_coordinates_reply_t*(xcb_connection_t* c, xcb_translate_coordinates_cookie_t cookie, xcb_generic_error_t** e));
+ MOCK_CONST_METHOD10(xcb_xfixes_create_pointer_barrier_checked, xcb_void_cookie_t(
+ xcb_connection_t* c,
+ xcb_xfixes_barrier_t barrier,
+ xcb_window_t window,
+ uint16_t x1,
+ uint16_t y1,
+ uint16_t x2,
+ uint16_t y2,
+ uint32_t directions,
+ uint16_t num_devices,
+ const uint16_t* devices));
+
+ // xcb-xinput
+ MOCK_CONST_METHOD3(xcb_input_xi_query_version, xcb_input_xi_query_version_cookie_t(xcb_connection_t* c, uint16_t major_version, uint16_t minor_version));
+ MOCK_CONST_METHOD3(xcb_input_xi_query_version_reply, xcb_input_xi_query_version_reply_t*(xcb_connection_t* c, xcb_input_xi_query_version_cookie_t cookie, xcb_generic_error_t** e));
+ MOCK_CONST_METHOD4(xcb_input_xi_select_events, xcb_void_cookie_t(xcb_connection_t* c, xcb_window_t window, uint16_t num_mask, const xcb_input_event_mask_t* masks));
+ MOCK_CONST_METHOD1(xcb_input_raw_button_press_axisvalues_length, int(const xcb_input_raw_button_press_event_t* R));
+ MOCK_CONST_METHOD1(xcb_input_raw_button_press_axisvalues_raw, xcb_input_fp3232_t*(const xcb_input_raw_button_press_event_t* R));
+
private:
static inline MockXcbInterface* self = nullptr;
};
diff --git a/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/XcbBaseTestFixture.h b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/XcbBaseTestFixture.h
index 8e9b008fc1..2a63a5f158 100644
--- a/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/XcbBaseTestFixture.h
+++ b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/XcbBaseTestFixture.h
@@ -22,6 +22,12 @@ namespace AzFramework
public:
void SetUp() override;
+ template
+ static xcb_generic_event_t MakeEvent(T event)
+ {
+ return *reinterpret_cast(&event);
+ }
+
protected:
testing::NiceMock m_interface;
xcb_connection_t m_connection{};
diff --git a/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/XcbInputDeviceKeyboardTests.cpp b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/XcbInputDeviceKeyboardTests.cpp
index 76d00eda50..7209875235 100644
--- a/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/XcbInputDeviceKeyboardTests.cpp
+++ b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/XcbInputDeviceKeyboardTests.cpp
@@ -21,12 +21,6 @@
#include "XcbBaseTestFixture.h"
#include "XcbTestApplication.h"
-template
-xcb_generic_event_t MakeEvent(T event)
-{
- return *reinterpret_cast(&event);
-}
-
namespace AzFramework
{
// Sets up default behavior for mock keyboard responses to xcb methods
diff --git a/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/XcbInputDeviceMouseTests.cpp b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/XcbInputDeviceMouseTests.cpp
new file mode 100644
index 0000000000..05784462d7
--- /dev/null
+++ b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/XcbInputDeviceMouseTests.cpp
@@ -0,0 +1,545 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#include
+#include
+
+#include
+
+#include
+#include
+
+#include "XcbBaseTestFixture.h"
+#include "XcbTestApplication.h"
+#include "Matchers.h"
+#include "Actions.h"
+
+namespace AzFramework
+{
+ // Sets up default behavior for mock keyboard responses to xcb methods
+ class XcbInputDeviceMouseTests
+ : public XcbBaseTestFixture
+ {
+ public:
+ void SetUp() override
+ {
+ using testing::Eq;
+ using testing::Field;
+ using testing::Return;
+ using testing::StrEq;
+ using testing::_;
+
+ XcbBaseTestFixture::SetUp();
+
+ ON_CALL(m_interface, xcb_get_setup(&m_connection))
+ .WillByDefault(Return(&s_xcbSetup));
+ ON_CALL(m_interface, xcb_setup_roots_iterator(&s_xcbSetup))
+ .WillByDefault(Return(xcb_screen_iterator_t{&s_xcbScreen}));
+
+ ON_CALL(m_interface, xcb_get_extension_data(&m_connection, &xcb_xfixes_id))
+ .WillByDefault(Return(&s_xfixesExtensionReply));
+ ON_CALL(m_interface, xcb_xfixes_query_version_reply(&m_connection, _, _))
+ .WillByDefault(ReturnMalloc(
+ /*response_type=*/(uint8_t)XCB_XFIXES_QUERY_VERSION,
+ /*pad0=*/(uint8_t)0,
+ /*sequence=*/(uint16_t)1,
+ /*length=*/0u,
+ /*major_version=*/5u,
+ /*minor_version=*/0u
+ ));
+
+ ON_CALL(m_interface, xcb_get_extension_data(&m_connection, &xcb_input_id))
+ .WillByDefault(Return(&s_xfixesExtensionReply));
+ ON_CALL(m_interface, xcb_input_xi_query_version_reply(&m_connection, _, _))
+ .WillByDefault(ReturnMalloc(
+ /*response_type=*/(uint8_t)XCB_INPUT_XI_QUERY_VERSION,
+ /*pad0=*/(uint8_t)0,
+ /*sequence=*/(uint16_t)1,
+ /*length=*/0u,
+ /*major_version=*/(uint16_t)2,
+ /*minor_version=*/(uint16_t)2
+ ));
+
+ // Set the default focus window
+ EXPECT_CALL(m_interface, xcb_intern_atom(&m_connection, 1, 18, StrEq("_NET_ACTIVE_WINDOW")))
+ .WillRepeatedly(Return(xcb_intern_atom_cookie_t{/*.sequence=*/ 1}));
+ ON_CALL(m_interface, xcb_intern_atom_reply(&m_connection, Field(&xcb_intern_atom_cookie_t::sequence, Eq(1)), _))
+ .WillByDefault(ReturnMalloc(
+ /*response_type=*/(uint8_t)XCB_INTERN_ATOM,
+ /*pad0=*/(uint8_t)0,
+ /*sequence=*/(uint16_t)1,
+ /*length=*/0u,
+ /*xcb_atom_t=*/s_netActiveWindowAtom
+ ));
+ ON_CALL(m_interface, xcb_get_property(&m_connection, 0, s_rootWindow, s_netActiveWindowAtom, XCB_ATOM_WINDOW, 0, 1))
+ .WillByDefault(Return(xcb_get_property_cookie_t{/*.sequence=*/ s_getActiveWindowPropertySequence}));
+ ON_CALL(m_interface, xcb_get_property_reply(&m_connection, Field(&xcb_get_property_cookie_t::sequence, Eq(s_getActiveWindowPropertySequence)), _))
+ .WillByDefault(ReturnMalloc(
+ /*response_type=*/(uint8_t)XCB_GET_PROPERTY,
+ /*format=*/(uint8_t)0,
+ /*sequence=*/(uint16_t)s_getActiveWindowPropertySequence,
+ /*length=*/0u,
+ /*type=*/XCB_ATOM_WINDOW,
+ /*bytes_after=*/0u,
+ /*value_len=*/1u
+ ));
+ ON_CALL(m_interface, xcb_get_property_value(Field(&xcb_get_property_reply_t::sequence, Eq(s_getActiveWindowPropertySequence))))
+ .WillByDefault(Return(const_cast(&s_nullWindow)));
+
+ ON_CALL(m_interface, xcb_get_geometry(&m_connection, _))
+ .WillByDefault(Return(xcb_get_geometry_cookie_t{/*.sequence=*/1}));
+ ON_CALL(m_interface, xcb_get_geometry_reply(&m_connection, Field(&xcb_get_geometry_cookie_t::sequence, Eq(1)), _))
+ .WillByDefault(ReturnMalloc(s_defaultWindowGeometry));
+ }
+
+ void PumpApplication()
+ {
+ m_application.PumpSystemEventLoopUntilEmpty();
+ m_application.TickSystem();
+ m_application.Tick();
+ }
+
+ protected:
+ static constexpr inline uint8_t s_xinputMajorOpcode = 131;
+ static constexpr inline xcb_window_t s_rootWindow = 1;
+ static constexpr inline xcb_window_t s_nullWindow = XCB_WINDOW_NONE;
+ static constexpr inline xcb_input_device_id_t s_virtualCorePointerId = 2;
+ static constexpr inline xcb_input_device_id_t s_physicalPointerDeviceId = 3;
+ static constexpr inline uint16_t s_screenWidthInPixels = 3840;
+ static constexpr inline uint16_t s_screenHeightInPixels = 2160;
+ static constexpr inline uint16_t s_getActiveWindowPropertySequence = 2160;
+ static constexpr inline xcb_atom_t s_netActiveWindowAtom = 1;
+ static constexpr inline xcb_setup_t s_xcbSetup{
+ /*.status=*/1,
+ /*.pad0=*/0,
+ /*.protocol_major_version=*/11,
+ /*.protocol_minor_version=*/0,
+ };
+ static inline xcb_screen_t s_xcbScreen{
+ /*.root=*/s_rootWindow,
+ /*.default_colormap=*/32,
+ /*.white_pixel=*/16777215,
+ /*.black_pixel=*/0,
+ /*.current_input_masks=*/0,
+ /*.width_in_pixels=*/s_screenWidthInPixels,
+ /*.height_in_pixels=*/s_screenHeightInPixels,
+ /*.width_in_millimeters=*/602,
+ /*.height_in_millimeters=*/341,
+ };
+ static constexpr inline xcb_query_extension_reply_t s_xfixesExtensionReply{
+ /*.response_type=*/XCB_QUERY_EXTENSION,
+ /*.pad0=*/0,
+ /*.sequence=*/1,
+ /*.length=*/0,
+ /*.present=*/1,
+ };
+ static constexpr inline xcb_query_extension_reply_t s_xinputExtensionReply{
+ /*.response_type=*/XCB_QUERY_EXTENSION,
+ /*.pad0=*/0,
+ /*.sequence=*/1,
+ /*.length=*/0,
+ /*.present=*/1,
+ /*.major_opcode=*/s_xinputMajorOpcode,
+ };
+ static constexpr inline xcb_get_geometry_reply_t s_defaultWindowGeometry{
+ /*.response_type=*/XCB_GET_GEOMETRY,
+ /*.depth=*/0,
+ /*.sequence=*/1,
+ /*.length=*/0,
+ /*.root=*/s_rootWindow,
+ /*.x=*/100,
+ /*.y=*/100,
+ /*.width=*/100,
+ /*.height=*/100,
+ /*.border_width=*/3,
+ /*.pad0[2]=*/{},
+ };
+ XcbTestApplication m_application{
+ /*enabledGamepadsCount=*/0,
+ /*keyboardEnabled=*/false,
+ /*motionEnabled=*/false,
+ /*mouseEnabled=*/true,
+ /*touchEnabled=*/false,
+ /*virtualKeyboardEnabled=*/false
+ };
+ };
+
+ struct MouseButtonTestData
+ {
+ xcb_button_index_t m_button;
+ };
+
+ class XcbInputDeviceMouseButtonTests
+ : public XcbInputDeviceMouseTests
+ , public testing::WithParamInterface
+ {
+ public:
+ static InputChannelId GetInputChannelIdForButton(const xcb_button_index_t button)
+ {
+ switch (button)
+ {
+ case XCB_BUTTON_INDEX_1:
+ return InputDeviceMouse::Button::Left;
+ case XCB_BUTTON_INDEX_2:
+ return InputDeviceMouse::Button::Right;
+ case XCB_BUTTON_INDEX_3:
+ return InputDeviceMouse::Button::Middle;
+ }
+ return InputChannelId{};
+ }
+
+ AZStd::array GetIdleChannelIdsForButton(const xcb_button_index_t button)
+ {
+ switch (button)
+ {
+ case XCB_BUTTON_INDEX_1:
+ return { InputDeviceMouse::Button::Right, InputDeviceMouse::Button::Middle, InputDeviceMouse::Button::Other1, InputDeviceMouse::Button::Other2 };
+ case XCB_BUTTON_INDEX_2:
+ return { InputDeviceMouse::Button::Left, InputDeviceMouse::Button::Middle, InputDeviceMouse::Button::Other1, InputDeviceMouse::Button::Other2 };
+ case XCB_BUTTON_INDEX_3:
+ return { InputDeviceMouse::Button::Left, InputDeviceMouse::Button::Right, InputDeviceMouse::Button::Other1, InputDeviceMouse::Button::Other2 };
+ case XCB_BUTTON_INDEX_4:
+ return { InputDeviceMouse::Button::Left, InputDeviceMouse::Button::Right, InputDeviceMouse::Button::Middle, InputDeviceMouse::Button::Other2 };
+ case XCB_BUTTON_INDEX_5:
+ return { InputDeviceMouse::Button::Left, InputDeviceMouse::Button::Right, InputDeviceMouse::Button::Middle, InputDeviceMouse::Button::Other1 };
+ }
+ return AZStd::array();
+ }
+ };
+
+ TEST_P(XcbInputDeviceMouseButtonTests, ButtonInputChannelsUpdateStateFromXcbEvents)
+ {
+ using testing::Each;
+ using testing::Eq;
+ using testing::NotNull;
+ using testing::Property;
+ using testing::Return;
+
+ // Set the expectations for the events that will be generated
+ // nullptr entries represent when the event queue is empty, and will cause
+ // PumpSystemEventLoopUntilEmpty to return
+ //
+ // Event pointers are freed by the calling code, so these actions
+ // malloc new copies
+ //
+ // The xcb mouse does not react to the `XCB_BUTTON_PRESS` /
+ // `XCB_BUTTON_RELEASE` events, but it will still receive those events
+ // from the X server.
+ EXPECT_CALL(m_interface, xcb_poll_for_event(&m_connection))
+ .WillOnce(ReturnMalloc(MakeEvent(xcb_input_raw_button_press_event_t{
+ /*response_type=*/XCB_GE_GENERIC,
+ /*extension=*/s_xinputMajorOpcode,
+ /*sequence=*/4,
+ /*length=*/2,
+ /*event_type=*/XCB_INPUT_RAW_BUTTON_PRESS,
+ /*deviceid=*/s_virtualCorePointerId,
+ /*time=*/3984920,
+ /*detail=*/GetParam().m_button,
+ /*sourceid=*/s_physicalPointerDeviceId,
+ /*valuators_len=*/2,
+ /*flags=*/0,
+ /*pad0[4]=*/{},
+ /*full_sequence=*/4
+ })))
+ .WillOnce(Return(nullptr))
+ .WillOnce(ReturnMalloc(MakeEvent(xcb_button_press_event_t{
+ /*response_type=*/XCB_BUTTON_PRESS,
+ /*detail=*/static_cast(GetParam().m_button),
+ /*sequence=*/4,
+ /*time=*/3984920,
+ /*root=*/s_rootWindow,
+ /*event=*/119537664,
+ /*child=*/0,
+ /*root_x=*/55,
+ /*root_y=*/1099,
+ /*event_x=*/55,
+ /*event_y=*/55,
+ /*state=*/0,
+ /*same_screen=*/1
+ })))
+ .WillOnce(Return(nullptr))
+ .WillOnce(ReturnMalloc(MakeEvent(xcb_input_raw_button_release_event_t{
+ /*response_type=*/XCB_GE_GENERIC,
+ /*extension=*/s_xinputMajorOpcode,
+ /*sequence=*/4,
+ /*length=*/2,
+ /*event_type=*/XCB_INPUT_RAW_BUTTON_RELEASE,
+ /*deviceid=*/s_virtualCorePointerId,
+ /*time=*/3984964,
+ /*detail=*/GetParam().m_button,
+ /*sourceid=*/s_physicalPointerDeviceId,
+ /*valuators_len=*/2,
+ /*flags=*/0,
+ /*pad0[4]=*/{},
+ /*full_sequence=*/4
+ })))
+ .WillOnce(Return(nullptr))
+ .WillOnce(ReturnMalloc(MakeEvent(xcb_button_release_event_t{
+ /*response_type=*/XCB_BUTTON_RELEASE,
+ /*detail=*/static_cast(GetParam().m_button),
+ /*sequence=*/4,
+ /*time=*/3984964,
+ /*root=*/s_rootWindow,
+ /*event=*/119537664,
+ /*child=*/0,
+ /*root_x=*/55,
+ /*root_y=*/1099,
+ /*event_x=*/55,
+ /*event_y=*/55,
+ /*state=*/XCB_KEY_BUT_MASK_BUTTON_1,
+ /*same_screen=*/1
+ })))
+ .WillOnce(Return(nullptr))
+ ;
+
+ m_application.Start();
+ InputSystemCursorRequestBus::Event(
+ InputDeviceMouse::Id,
+ &InputSystemCursorRequests::SetSystemCursorState,
+ SystemCursorState::ConstrainedAndHidden);
+
+ const InputChannel* activeButtonChannel = InputChannelRequests::FindInputChannel(GetInputChannelIdForButton(GetParam().m_button));
+ const auto inactiveButtonChannels = [this]()
+ {
+ const auto inactiveButtonChannelIds = GetIdleChannelIdsForButton(GetParam().m_button);
+ AZStd::array channels{};
+ AZStd::transform(begin(inactiveButtonChannelIds), end(inactiveButtonChannelIds), begin(channels), [](const InputChannelId& id)
+ {
+ return InputChannelRequests::FindInputChannel(id);
+ });
+ return channels;
+ }();
+
+ ASSERT_TRUE(activeButtonChannel);
+ ASSERT_THAT(inactiveButtonChannels, Each(NotNull()));
+
+ EXPECT_THAT(activeButtonChannel->GetState(), Eq(InputChannel::State::Idle));
+ EXPECT_THAT(inactiveButtonChannels, Each(Property(&InputChannel::GetState, Eq(InputChannel::State::Idle))));
+
+ PumpApplication();
+
+ EXPECT_THAT(activeButtonChannel->GetState(), Eq(InputChannel::State::Began));
+ EXPECT_THAT(inactiveButtonChannels, Each(Property(&InputChannel::GetState, Eq(InputChannel::State::Idle))));
+
+ PumpApplication();
+
+ EXPECT_THAT(activeButtonChannel->GetState(), Eq(InputChannel::State::Updated));
+ EXPECT_THAT(inactiveButtonChannels, Each(Property(&InputChannel::GetState, Eq(InputChannel::State::Idle))));
+
+ PumpApplication();
+
+ EXPECT_THAT(activeButtonChannel->GetState(), Eq(InputChannel::State::Ended));
+ EXPECT_THAT(inactiveButtonChannels, Each(Property(&InputChannel::GetState, Eq(InputChannel::State::Idle))));
+
+ PumpApplication();
+
+ EXPECT_THAT(activeButtonChannel->GetState(), Eq(InputChannel::State::Idle));
+ EXPECT_THAT(inactiveButtonChannels, Each(Property(&InputChannel::GetState, Eq(InputChannel::State::Idle))));
+ }
+
+ INSTANTIATE_TEST_CASE_P(
+ AllButtons,
+ XcbInputDeviceMouseButtonTests,
+ testing::Values(
+ MouseButtonTestData{ XCB_BUTTON_INDEX_1 },
+ MouseButtonTestData{ XCB_BUTTON_INDEX_2 },
+ MouseButtonTestData{ XCB_BUTTON_INDEX_3 }
+ // XCB_BUTTON_INDEX_4 and XCB_BUTTON_INDEX_5 map to positive and
+ // negative scroll wheel events, which are handled as motion events
+ )
+ );
+
+ TEST_F(XcbInputDeviceMouseTests, MovementInputChannelsUpdateStateFromXcbEvents)
+ {
+ using testing::Each;
+ using testing::Eq;
+ using testing::FloatEq;
+ using testing::NotNull;
+ using testing::Property;
+ using testing::Return;
+
+ // Set the expectations for the events that will be generated
+ // nullptr entries represent when the event queue is empty, and will cause
+ // PumpSystemEventLoopUntilEmpty to return
+ //
+ // Event pointers are freed by the calling code, so these actions
+ // malloc new copies
+ //
+ // The xcb mouse does not react to the `XCB_MOTION_NOTIFY` event, but
+ // it will still receive it from the X server.
+ EXPECT_CALL(m_interface, xcb_poll_for_event(&m_connection))
+ .WillOnce(ReturnMalloc(MakeEvent(xcb_input_raw_motion_event_t{
+ /*response_type=*/XCB_GE_GENERIC,
+ /*extension=*/s_xinputMajorOpcode,
+ /*sequence=*/5,
+ /*length=*/10,
+ /*event_type=*/XCB_INPUT_RAW_MOTION,
+ /*deviceid=*/s_virtualCorePointerId,
+ /*time=*/0, // use the time value to identify each event
+ /*detail=*/XCB_MOTION_NORMAL,
+ /*sourceid=*/s_physicalPointerDeviceId,
+ /*valuators_len=*/2, // number of axes that have values for this event
+ /*flags=*/0,
+ /*pad0[4]=*/{},
+ /*full_sequence=*/5,
+ })))
+ .WillOnce(Return(nullptr))
+ .WillOnce(ReturnMalloc(MakeEvent(xcb_motion_notify_event_t{
+ /*response_type=*/XCB_MOTION_NOTIFY,
+ /*detail=*/XCB_MOTION_NORMAL,
+ /*sequence=*/5,
+ /*time=*/1, // use the time value to identify each event
+ /*root=*/s_rootWindow,
+ /*event=*/127926272,
+ /*child=*/0,
+ /*root_x=*/95,
+ /*root_y=*/1079,
+ /*event_x=*/95,
+ /*event_y=*/20,
+ /*state=*/0,
+ /*same_screen=*/1,
+ })))
+ .WillOnce(Return(nullptr))
+ ;
+
+ AZStd::array axisValues
+ {
+ xcb_input_fp3232_t{ /*.integral=*/ 1, /*.fraction=*/0 }, // x motion
+ xcb_input_fp3232_t{ /*.integral=*/ 2, /*.fraction=*/0 } // y motion
+ };
+
+ EXPECT_CALL(m_interface, xcb_input_raw_button_press_axisvalues_length(testing::Field(&xcb_input_raw_button_press_event_t::time, 0)))
+ .WillRepeatedly(testing::Return(2)); // x and y axis
+ EXPECT_CALL(m_interface, xcb_input_raw_button_press_axisvalues_raw(testing::Field(&xcb_input_raw_button_press_event_t::time, 0)))
+ .WillRepeatedly(testing::Return(axisValues.data())); // x and y axis
+
+ m_application.Start();
+ InputSystemCursorRequestBus::Event(
+ InputDeviceMouse::Id,
+ &InputSystemCursorRequests::SetSystemCursorState,
+ SystemCursorState::ConstrainedAndHidden);
+
+ const InputChannel* xMotionChannel = InputChannelRequests::FindInputChannel(InputDeviceMouse::Movement::X);
+ const InputChannel* yMotionChannel = InputChannelRequests::FindInputChannel(InputDeviceMouse::Movement::Y);
+ ASSERT_TRUE(xMotionChannel);
+ ASSERT_TRUE(yMotionChannel);
+
+ EXPECT_THAT(xMotionChannel->GetState(), Eq(InputChannel::State::Idle));
+ EXPECT_THAT(yMotionChannel->GetState(), Eq(InputChannel::State::Idle));
+ EXPECT_THAT(xMotionChannel->GetValue(), FloatEq(0.0f));
+ EXPECT_THAT(yMotionChannel->GetValue(), FloatEq(0.0f));
+
+ PumpApplication();
+
+ EXPECT_THAT(xMotionChannel->GetState(), Eq(InputChannel::State::Began));
+ EXPECT_THAT(yMotionChannel->GetState(), Eq(InputChannel::State::Began));
+ EXPECT_THAT(xMotionChannel->GetValue(), FloatEq(1.0f));
+ EXPECT_THAT(yMotionChannel->GetValue(), FloatEq(2.0f));
+
+ PumpApplication();
+
+ EXPECT_THAT(xMotionChannel->GetState(), Eq(InputChannel::State::Ended));
+ EXPECT_THAT(yMotionChannel->GetState(), Eq(InputChannel::State::Ended));
+ EXPECT_THAT(xMotionChannel->GetValue(), FloatEq(0.0f));
+ EXPECT_THAT(yMotionChannel->GetValue(), FloatEq(0.0f));
+ }
+
+ struct GetCursorPositionParam
+ {
+ int16_t m_x;
+ int16_t m_y;
+ };
+
+ class XcbGetSystemCursorPositionTests
+ : public XcbInputDeviceMouseTests
+ , public testing::WithParamInterface
+ {
+ };
+
+ TEST_P(XcbGetSystemCursorPositionTests, GetSystemCursorPositionNormalizedReturnsCorrectValue)
+ {
+ using testing::Eq;
+ using testing::Field;
+ using testing::Return;
+ using testing::_;
+
+ xcb_window_t focusWindow = 42;
+ const xcb_query_pointer_reply_t queryPointerReply{
+ /*.response_type=*/XCB_QUERY_POINTER,
+ /*.same_screen=*/1,
+ /*.sequence=*/0,
+ /*.length=*/1,
+ /*.root=*/s_rootWindow,
+ /*.child=*/focusWindow,
+ /*.root_x=*/static_cast