diff --git a/Source/Integration/InputDeviceTracker.cpp b/Source/Integration/InputDeviceTracker.cpp new file mode 100644 index 00000000..6d2d4fb1 --- /dev/null +++ b/Source/Integration/InputDeviceTracker.cpp @@ -0,0 +1,105 @@ + +#include "InputDeviceTracker.h" +#include "Framework/Application/SlateApplication.h" +#include "GenericPlatform/GenericApplicationMessageHandler.h" +#include "InputCoreTypes.h" + +// Last observed device classification. Read with no +// synchronization; updates happen on the game thread +// from Slate's input pipeline, and stale reads on +// other threads are acceptable for this use case. +// +static ElxControllerType GLastControllerType = ElxControllerType::KeyboardMouse; + +// Keywords identifying a PlayStation-family gamepad. +// Matched case-insensitively against the InputDeviceName +// and HardwareDeviceIdentifier fields of the current +// FInputDeviceScope. +// +static const TCHAR* const PlaystationKeywords[] = { + TEXT("Playstation"), + TEXT("PS3"), + TEXT("PS4"), + TEXT("PS5"), + TEXT("PS6"), + TEXT("PS7"), + TEXT("Dualsense"), + TEXT("Dualshock"), +}; + +namespace +{ + bool ContainsAnyPlaystationKeyword(const FString& Haystack) + { + for (const TCHAR* Keyword : PlaystationKeywords) + { + if (Haystack.Contains(Keyword, ESearchCase::IgnoreCase)) + { + return true; + } + } + return false; + } + + // Classifies the active gamepad by scanning the current + // FInputDeviceScope. Defaults to Xbox; switches to + // PlayStation only on a keyword match. + // + ElxControllerType ClassifyGamepadFromScope() + { + const FInputDeviceScope* Scope = FInputDeviceScope::GetCurrent(); + if (Scope != nullptr) + { + if (ContainsAnyPlaystationKeyword(Scope->InputDeviceName.ToString()) || + ContainsAnyPlaystationKeyword(Scope->HardwareDeviceIdentifier)) + { + return ElxControllerType::PlayStationGamepad; + } + } + return ElxControllerType::XboxGamepad; + } +} + +bool FInputDeviceTrackerProcessor::HandleKeyDownEvent(FSlateApplication& SlateApp, const FKeyEvent& KeyEvent) +{ + if (KeyEvent.GetKey().IsGamepadKey()) + { + GLastControllerType = ClassifyGamepadFromScope(); + } + else + { + GLastControllerType = ElxControllerType::KeyboardMouse; + } + return false; +} + +bool FInputDeviceTrackerProcessor::HandleMouseButtonDownEvent(FSlateApplication& SlateApp, const FPointerEvent& MouseEvent) +{ + GLastControllerType = ElxControllerType::KeyboardMouse; + return false; +} + +void UlxInputDeviceTracker::Initialize(FSubsystemCollectionBase& Collection) +{ + Super::Initialize(Collection); + if (FSlateApplication::IsInitialized()) + { + Processor = MakeShared(); + FSlateApplication::Get().RegisterInputPreProcessor(Processor); + } +} + +void UlxInputDeviceTracker::Deinitialize() +{ + if (Processor.IsValid() && FSlateApplication::IsInitialized()) + { + FSlateApplication::Get().UnregisterInputPreProcessor(Processor); + } + Processor.Reset(); + Super::Deinitialize(); +} + +ElxControllerType UlxInputDeviceTracker::GetLastControllerType() +{ + return GLastControllerType; +} diff --git a/Source/Integration/InputDeviceTracker.h b/Source/Integration/InputDeviceTracker.h new file mode 100644 index 00000000..81927b0a --- /dev/null +++ b/Source/Integration/InputDeviceTracker.h @@ -0,0 +1,63 @@ +//////////////////////////////////////////////////////////// +// +// InputDeviceTracker.h +// +// Tracks the most recently used input device, classifying +// it as keyboard/mouse, Xbox gamepad, or PlayStation +// gamepad. The subsystem registers a Slate input +// preprocessor that watches button-down events; analog +// and mouse-move events are ignored. Read the current +// classification via the static accessor. +// +//////////////////////////////////////////////////////////// + +#pragma once + +#include "CoreMinimal.h" +#include "Subsystems/GameInstanceSubsystem.h" +#include "Framework/Application/IInputProcessor.h" +#include "Common.h" +#include "InputDeviceTracker.generated.h" + +//////////////////////////////////////////////////////////// +// +// FInputDeviceTrackerProcessor +// +// Slate input preprocessor. Updates the device-class +// static on each button-down event. Never consumes +// events (always returns false). +// +//////////////////////////////////////////////////////////// + +class FInputDeviceTrackerProcessor : public IInputProcessor +{ +public: + virtual void Tick(const float DeltaTime, FSlateApplication& SlateApp, TSharedRef Cursor) override {} + virtual bool HandleKeyDownEvent(FSlateApplication& SlateApp, const FKeyEvent& KeyEvent) override; + virtual bool HandleMouseButtonDownEvent(FSlateApplication& SlateApp, const FPointerEvent& MouseEvent) override; +}; + +//////////////////////////////////////////////////////////// +// +// UlxInputDeviceTracker +// +//////////////////////////////////////////////////////////// + +UCLASS(MinimalAPI) +class UlxInputDeviceTracker : public UGameInstanceSubsystem +{ + GENERATED_BODY() + +public: + virtual void Initialize(FSubsystemCollectionBase& Collection) override; + virtual void Deinitialize() override; + + // Returns the classification of the most recently used + // input device. Defaults to KeyboardMouse until the + // first gamepad button event is observed. + // + static ElxControllerType GetLastControllerType(); + +private: + TSharedPtr Processor; +}; diff --git a/Source/Integration/Integration.Build.cs b/Source/Integration/Integration.Build.cs index e487caca..99c0102c 100644 --- a/Source/Integration/Integration.Build.cs +++ b/Source/Integration/Integration.Build.cs @@ -31,6 +31,7 @@ public class Integration : ModuleRules "UMG", "UMGEditor", "EditorSubsystem", + "ApplicationCore", }); PrivateDependencyModuleNames.Add("Slate"); diff --git a/Source/Integration/PromptWidget.cpp b/Source/Integration/PromptWidget.cpp index 2dfbd9e6..325eeb89 100644 --- a/Source/Integration/PromptWidget.cpp +++ b/Source/Integration/PromptWidget.cpp @@ -1,4 +1,5 @@ #include "PromptWidget.h" +#include "InputDeviceTracker.h" #include "UtilityLibrary.h" #include "PlayerControllerBase.h" #include "InputAction.h" @@ -194,7 +195,7 @@ void UlxPromptWidget::SetKeysFromBindings(const UInputMappingContext* InputMappi bool UlxPromptWidget::OnTick(float DeltaTime) { - ElxControllerType Type = UlxUtilityLibrary::DetectControllerType(GetOwningLocalPlayer()); + ElxControllerType Type = UlxInputDeviceTracker::GetLastControllerType(); if (ControllerType != Type) { ControllerType = Type; @@ -207,7 +208,7 @@ TSharedRef UlxPromptWidget::RebuildWidget() { if (!IsDesignTime()) { - ControllerType = UlxUtilityLibrary::DetectControllerType(GetOwningLocalPlayer()); + ControllerType = UlxInputDeviceTracker::GetLastControllerType(); TickHandle = FTSTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateUObject(this, &UlxPromptWidget::OnTick)); } diff --git a/Source/Integration/UtilityLibrary.cpp b/Source/Integration/UtilityLibrary.cpp index 1f5123a0..44ee69f8 100644 --- a/Source/Integration/UtilityLibrary.cpp +++ b/Source/Integration/UtilityLibrary.cpp @@ -14,7 +14,6 @@ #include "EnhancedInputComponent.h" #include "Animation/AnimSequenceBase.h" #include "GameFramework/Pawn.h" -#include "GameFramework/InputDeviceSubsystem.h" #include "GameFramework/InputSettings.h" @@ -278,17 +277,3 @@ void UlxUtilityLibrary::ValidateLuaExpr( Status = w.ValidateLuaExpr(Code, ErrorMessage); } -ElxControllerType UlxUtilityLibrary::DetectControllerType(ULocalPlayer *Player) -{ - UInputDeviceSubsystem *IDS = GEngine->GetEngineSubsystem(); - if (!IDS) return ElxControllerType::KeyboardMouse; - - FName Id = IDS->GetMostRecentlyUsedHardwareDevice(Player->GetPlatformUserId()).HardwareDeviceIdentifier; - - if (Id == TEXT("ps3") || Id == TEXT("ps4") || Id == TEXT("ps5")) return ElxControllerType::PlayStationGamepad; - if (Id == TEXT("xbox360") || Id == TEXT("xboxone")) return ElxControllerType::XboxGamepad; - - // Unknown or unrecognized device — assume keyboard/mouse - return ElxControllerType::KeyboardMouse; -} - diff --git a/Source/Integration/UtilityLibrary.h b/Source/Integration/UtilityLibrary.h index b995b4f9..dfa0eb93 100644 --- a/Source/Integration/UtilityLibrary.h +++ b/Source/Integration/UtilityLibrary.h @@ -179,9 +179,4 @@ public: // UFUNCTION(BlueprintCallable, meta = (WorldContext = "context"), Category = "Luprex|Utility") static void ValidateLuaExpr(ElxLuaSyntaxCheck &Status, FString &ErrorMessage, UObject *context, const FString &Code); - - // Determine what type of controller the game is currently using - // - UFUNCTION(BlueprintCallable, category="Luprex|Utility") - static ElxControllerType DetectControllerType(ULocalPlayer *Player); };