--- Engine/Plugins/Developer/VisualStudioCodeSourceCodeAccess/Source/VisualStudioCodeSourceCodeAccess/Private/VisualStudioCodeSourceCodeAccessor.cpp.orig 2026-05-08 14:11:02.262757499 -0400 +++ Engine/Plugins/Developer/VisualStudioCodeSourceCodeAccess/Source/VisualStudioCodeSourceCodeAccess/Private/VisualStudioCodeSourceCodeAccessor.cpp 2026-05-05 15:36:35.232152395 -0400 @@ -149,7 +149,7 @@ FString SolutionDir = GetSolutionPath(); TArray Args; Args.Add(MakePath(SolutionDir)); - Args.Add(TEXT("-g ") + MakePath(FullPath) + FString::Printf(TEXT(":%d:%d"), LineNumber, ColumnNumber)); + Args.Add(TEXT("-g ") + MakePath(FullPath + FString::Printf(TEXT(":%d:%d"), LineNumber, ColumnNumber))); return Launch(Args); } --- Engine/Source/Editor/UnrealEd/Private/SourceCodeNavigation.cpp.orig 2026-05-08 14:11:02.370759731 -0400 +++ Engine/Source/Editor/UnrealEd/Private/SourceCodeNavigation.cpp 2026-05-05 15:36:35.232353795 -0400 @@ -557,7 +557,7 @@ ISourceCodeAccessModule& SourceCodeAccessModule = FModuleManager::LoadModuleChecked("SourceCodeAccess"); ISourceCodeAccessor& SourceCodeAccessor = SourceCodeAccessModule.GetAccessor(); -#if PLATFORM_WINDOWS +#if PLATFORM_WINDOWS || PLATFORM_LINUX FString SourceFileName; uint32 SourceLineNumber = 1; uint32 SourceColumnNumber = 0; @@ -716,8 +716,8 @@ } UE_LOG(LogSelectionDetails, Warning, TEXT("NavigateToFunctionSource: Unable to look up symbol: %s in module:%s"), *FunctionSymbolName, *FunctionModuleName); - -#endif // PLATFORM_WINDOWS + +#endif // PLATFORM_WINDOWS || PLATFORM_LINUX } --- Engine/Source/Runtime/ApplicationCore/Private/Linux/LinuxPlatformApplicationMisc.cpp.orig 2026-05-08 14:11:02.477761942 -0400 +++ Engine/Source/Runtime/ApplicationCore/Private/Linux/LinuxPlatformApplicationMisc.cpp 2026-05-05 15:36:35.232635087 -0400 @@ -317,6 +317,9 @@ // Furthermore SDL hides the mouse which we prevent by setting SDL_HINT_MOUSE_RELATIVE_CURSOR_VISIBLE SDL_SetHint(SDL_HINT_MOUSE_RELATIVE_CURSOR_VISIBLE, "1"); // When relative mouse mode is active, don't hide cursor. + // Unreal does its own dynamic capturing, we don't need SDL to do it. + SDL_SetHint(SDL_HINT_MOUSE_AUTO_CAPTURE, "0"); + // If we're rendering offscreen, use the "dummy" SDL video driver if (FParse::Param(FCommandLine::Get(), TEXT("RenderOffScreen")) && !getenv("SDL_VIDEODRIVER")) { --- Engine/Source/Runtime/Core/Private/Unix/UnixPlatformStackWalk.cpp.orig 2026-05-08 14:11:02.584764153 -0400 +++ Engine/Source/Runtime/Core/Private/Unix/UnixPlatformStackWalk.cpp 2026-05-05 15:36:35.232750204 -0400 @@ -15,6 +15,7 @@ #include "HAL/ExceptionHandling.h" #include "HAL/PlatformProcess.h" #include "HAL/PlatformTime.h" +#include "Modules/ModuleManager.h" #include "AutoRTFM.h" #include @@ -1063,3 +1064,69 @@ } ReportLock.Unlock(); } + +bool FUnixPlatformStackWalk::GetFunctionDefinitionLocation(const FString& FunctionSymbolName, const FString& FunctionModuleName, FString& OutPathname, uint32& OutLineNumber, uint32& OutColumnNumber) +{ + // Find the .so path for this module. + FString ModulePath; + TArray AllModules; + FModuleManager::Get().QueryModules(AllModules); + for (const FModuleStatus& Status : AllModules) + { + if (FPaths::GetBaseFilename(Status.FilePath) == FunctionModuleName) + { + ModulePath = Status.FilePath; + break; + } + } + if (ModulePath.IsEmpty()) + { + return false; + } + + // Debug symbols are in a separate .debug file alongside the .so. + FString DebugPath = FPaths::ChangeExtension(ModulePath, TEXT("debug")); + if (!FPaths::FileExists(DebugPath)) + { + return false; + } + + // Use lldb to look up the source file and line number. + // Run: lldb -b -o "image lookup -v -n ClassName::FuncName" + FString LldbParams = FString::Printf(TEXT("-b -o \"image lookup -v -n %s\" \"%s\""), *FunctionSymbolName, *DebugPath); + int32 ReturnCode = 0; + FString AllOutput; + FString Errors; + FPlatformProcess::ExecProcess(TEXT("/usr/bin/lldb"), *LldbParams, &ReturnCode, &AllOutput, &Errors); + if (ReturnCode != 0) + { + return false; + } + + // Parse the LineEntry from lldb verbose output. + // Format: "LineEntry: [0x...-0x...): /path/to/file.cpp:132" + TArray Lines; + AllOutput.ParseIntoArrayLines(Lines); + for (const FString& Line : Lines) + { + FString Trimmed = Line.TrimStartAndEnd(); + if (!Trimmed.StartsWith(TEXT("LineEntry:"))) + continue; + + int32 ParenIndex = Trimmed.Find(TEXT("): ")); + if (ParenIndex == INDEX_NONE) + continue; + FString FileAndLine = Trimmed.Mid(ParenIndex + 3); + + int32 ColonIndex; + if (!FileAndLine.FindLastChar(TCHAR(':'), ColonIndex)) + continue; + + OutPathname = FileAndLine.Left(ColonIndex); + OutLineNumber = FCString::Atoi(*FileAndLine.Mid(ColonIndex + 1)); + OutColumnNumber = 0; + return true; + } + + return false; +} --- Engine/Source/Runtime/Core/Public/Unix/UnixPlatformStackWalk.h.orig 2026-05-08 14:11:02.692766385 -0400 +++ Engine/Source/Runtime/Core/Public/Unix/UnixPlatformStackWalk.h 2026-05-05 15:36:35.232861435 -0400 @@ -24,6 +24,8 @@ static CORE_API void ThreadStackWalkAndDump(ANSICHAR* HumanReadableString, SIZE_T HumanReadableStringSize, int32 IgnoreCount, uint32 ThreadId); static CORE_API int32 GetProcessModuleCount(); static CORE_API int32 GetProcessModuleSignatures(FStackWalkModuleInfo *ModuleSignatures, const int32 ModuleSignaturesSize); + + static CORE_API bool GetFunctionDefinitionLocation(const FString& FunctionSymbolName, const FString& FunctionModuleName, FString& OutPathname, uint32& OutLineNumber, uint32& OutColumnNumber); }; typedef FUnixPlatformStackWalk FPlatformStackWalk; --- Engine/Source/Runtime/ApplicationCore/Public/Linux/LinuxApplication.h.orig 2026-05-08 14:11:02.802768658 -0400 +++ Engine/Source/Runtime/ApplicationCore/Public/Linux/LinuxApplication.h 2026-05-08 14:09:14.778161399 -0400 @@ -265,6 +265,12 @@ /** The input device Id of the controller that can be used to find the matching ULocalPlayer */ FInputDeviceId DeviceId; + /** SDL gamepad type string (e.g. "ps4", "xboxone"), used as HardwareDeviceIdentifier in FInputDeviceScope */ + FName GamepadType; + + /** SDL gamepad name (human-readable device name), used as InputDeviceName in FInputDeviceScope */ + FName GamepadName; + /** Store axis values from events here to be handled once per frame. */ TMap AxisEvents; --- Engine/Source/Runtime/ApplicationCore/Private/Linux/LinuxApplication.cpp.orig 2026-05-08 14:11:02.910770890 -0400 +++ Engine/Source/Runtime/ApplicationCore/Private/Linux/LinuxApplication.cpp 2026-05-08 14:09:14.778345206 -0400 @@ -13,6 +13,13 @@ #include "IHapticDevice.h" #include "GenericPlatform/GenericPlatformInputDeviceMapper.h" +namespace UE::LinuxInput +{ + static const FName InputClassName = TEXT("LinuxApplication"); + static const FString KBMInputHardwareName = TEXT("KBM"); + static const FString TouchInputHardwareName = TEXT("MobileTouch"); +} + // // GameController thresholds // @@ -320,6 +327,7 @@ { case SDL_EVENT_KEY_DOWN: { + FInputDeviceScope InputScope(nullptr, UE::LinuxInput::InputClassName, IPlatformInputDeviceMapper::Get().GetDefaultInputDevice().GetId(), UE::LinuxInput::KBMInputHardwareName); const SDL_KeyboardEvent &KeyEvent = Event.key; SDL_Keycode KeySym = KeyEvent.key; const uint32 CharCode = CharCodeFromSDLKeySym(KeySym); @@ -342,6 +350,7 @@ break; case SDL_EVENT_KEY_UP: { + FInputDeviceScope InputScope(nullptr, UE::LinuxInput::InputClassName, IPlatformInputDeviceMapper::Get().GetDefaultInputDevice().GetId(), UE::LinuxInput::KBMInputHardwareName); const SDL_KeyboardEvent &KeyEvent = Event.key; const SDL_Keycode KeySym = KeyEvent.key; const uint32 CharCode = CharCodeFromSDLKeySym(KeySym); @@ -352,6 +361,7 @@ break; case SDL_EVENT_TEXT_INPUT: { + FInputDeviceScope InputScope(nullptr, UE::LinuxInput::InputClassName, IPlatformInputDeviceMapper::Get().GetDefaultInputDevice().GetId(), UE::LinuxInput::KBMInputHardwareName); // Slate now gets all its text from here, I hope. const bool bIsRepeated = false; //Event.key.repeat != 0; const FString TextStr(UTF8_TO_TCHAR(Event.text.text)); @@ -363,6 +373,7 @@ break; case SDL_EVENT_MOUSE_MOTION: { + FInputDeviceScope InputScope(nullptr, UE::LinuxInput::InputClassName, IPlatformInputDeviceMapper::Get().GetDefaultInputDevice().GetId(), UE::LinuxInput::KBMInputHardwareName); SDL_MouseMotionEvent motionEvent = Event.motion; FLinuxCursor *LinuxCursor = (FLinuxCursor*)Cursor.Get(); LinuxCursor->InvalidateCaches(); @@ -406,6 +417,7 @@ case SDL_EVENT_MOUSE_BUTTON_DOWN: case SDL_EVENT_MOUSE_BUTTON_UP: { + FInputDeviceScope InputScope(nullptr, UE::LinuxInput::InputClassName, IPlatformInputDeviceMapper::Get().GetDefaultInputDevice().GetId(), UE::LinuxInput::KBMInputHardwareName); SDL_MouseButtonEvent buttonEvent = Event.button; EMouseButtons::Type button; @@ -484,6 +496,7 @@ break; case SDL_EVENT_MOUSE_WHEEL: { + FInputDeviceScope InputScope(nullptr, UE::LinuxInput::InputClassName, IPlatformInputDeviceMapper::Get().GetDefaultInputDevice().GetId(), UE::LinuxInput::KBMInputHardwareName); SDL_MouseWheelEvent *WheelEvent = &Event.wheel; float Amount = (float)WheelEvent->y * fMouseWheelScrollAccel; @@ -515,6 +528,7 @@ SDLControllerState &ControllerState = ControllerStates[caxisEvent.which]; FPlatformUserId UserId = IPlatformInputDeviceMapper::Get().GetUserForInputDevice(ControllerState.DeviceId); + FInputDeviceScope InputScope(nullptr, ControllerState.GamepadName, ControllerState.DeviceId.GetId(), ControllerState.GamepadType.ToString()); switch (caxisEvent.axis) { @@ -740,15 +754,17 @@ if (Button != FGamepadKeyNames::Invalid) { - FPlatformUserId UserId = IPlatformInputDeviceMapper::Get().GetUserForInputDevice(ControllerStates[cbuttonEvent.which].DeviceId); + SDLControllerState& ButtonControllerState = ControllerStates[cbuttonEvent.which]; + FPlatformUserId UserId = IPlatformInputDeviceMapper::Get().GetUserForInputDevice(ButtonControllerState.DeviceId); + FInputDeviceScope InputScope(nullptr, ButtonControllerState.GamepadName, ButtonControllerState.DeviceId.GetId(), ButtonControllerState.GamepadType.ToString()); if(cbuttonEvent.type == SDL_EVENT_GAMEPAD_BUTTON_DOWN) { - MessageHandler->OnControllerButtonPressed(Button, UserId, ControllerStates[cbuttonEvent.which].DeviceId, false); + MessageHandler->OnControllerButtonPressed(Button, UserId, ButtonControllerState.DeviceId, false); } else { - MessageHandler->OnControllerButtonReleased(Button, UserId, ControllerStates[cbuttonEvent.which].DeviceId, false); + MessageHandler->OnControllerButtonReleased(Button, UserId, ButtonControllerState.DeviceId, false); } } } @@ -1018,6 +1034,7 @@ case SDL_EVENT_FINGER_DOWN: { + FInputDeviceScope InputScope(nullptr, UE::LinuxInput::InputClassName, IPlatformInputDeviceMapper::Get().GetDefaultInputDevice().GetId(), UE::LinuxInput::TouchInputHardwareName); UE_LOG(LogLinuxWindow, Verbose, TEXT("Finger %llu is down at (%f, %f)"), Event.tfinger.fingerID, Event.tfinger.x, Event.tfinger.y); // touch events can have no window associated with them, in that case ignore (with a warning) @@ -1053,6 +1070,7 @@ break; case SDL_EVENT_FINGER_UP: { + FInputDeviceScope InputScope(nullptr, UE::LinuxInput::InputClassName, IPlatformInputDeviceMapper::Get().GetDefaultInputDevice().GetId(), UE::LinuxInput::TouchInputHardwareName); UE_LOG(LogLinuxWindow, Verbose, TEXT("Finger %llu is up at (%f, %f)"), Event.tfinger.fingerID, Event.tfinger.x, Event.tfinger.y); // touch events can have no window associated with them, in that case ignore (with a warning) @@ -1087,6 +1105,7 @@ break; case SDL_EVENT_FINGER_MOTION: { + FInputDeviceScope InputScope(nullptr, UE::LinuxInput::InputClassName, IPlatformInputDeviceMapper::Get().GetDefaultInputDevice().GetId(), UE::LinuxInput::TouchInputHardwareName); // touch events can have no window associated with them, in that case ignore (with a warning) if (LIKELY(!bWindowlessEvent)) { @@ -1196,6 +1215,7 @@ IPlatformInputDeviceMapper& Mapper = IPlatformInputDeviceMapper::Get(); for(auto ControllerIt = ControllerStates.CreateIterator(); ControllerIt; ++ControllerIt) { + FInputDeviceScope InputScope(nullptr, ControllerIt.Value().GamepadName, ControllerIt.Value().DeviceId.GetId(), ControllerIt.Value().GamepadType.ToString()); for(auto Event = ControllerIt.Value().AxisEvents.CreateConstIterator(); Event; ++Event) { FPlatformUserId UserId = Mapper.GetUserForInputDevice(ControllerIt.Value().DeviceId); @@ -2075,6 +2095,9 @@ UE_LOG(LogLinux, Verbose, TEXT("Adding controller %i '%s'"), FirstUnusedIndex, UTF8_TO_TCHAR(SDL_GetGamepadName(Controller))); auto& ControllerState = ControllerStates.Add(Id); ControllerState.Controller = Controller; + ControllerState.GamepadName = FName(UTF8_TO_TCHAR(SDL_GetGamepadName(Controller))); + const char* GamepadTypeStr = SDL_GetGamepadStringForType(SDL_GetGamepadType(Controller)); + ControllerState.GamepadType = FName(GamepadTypeStr ? UTF8_TO_TCHAR(GamepadTypeStr) : TEXT("unknown")); FPlatformUserId UserId = FPlatformUserId::CreateFromInternalId(FirstUnusedIndex); DeviceMapper.RemapControllerIdToPlatformUserAndDevice(FirstUnusedIndex, UserId, ControllerState.DeviceId);