diff --git a/Dalamud/Game/ClientState/GamepadState.cs b/Dalamud/Game/ClientState/GamepadState.cs
index 0c33f28bf..7e04a7a71 100644
--- a/Dalamud/Game/ClientState/GamepadState.cs
+++ b/Dalamud/Game/ClientState/GamepadState.cs
@@ -3,6 +3,7 @@
using Dalamud.Game.ClientState.Structs;
using Dalamud.Hooking;
using ImGuiNET;
+using Serilog;
namespace Dalamud.Game.ClientState
{
@@ -28,6 +29,9 @@ namespace Dalamud.Game.ClientState
/// Resolver knowing the pointer to the GamepadPoll function.
public GamepadState(ClientStateAddressResolver resolver)
{
+#if DEBUG
+ Log.Verbose("GamepadPoll address {GamepadPoll}", resolver.GamepadPoll);
+#endif
this.gamepadPoll = new Hook(
resolver.GamepadPoll,
(ControllerPoll)this.GamepadPollDetour);
@@ -118,10 +122,21 @@ namespace Dalamud.Game.ClientState
///
internal ushort ButtonsRepeat { get; private set; }
+ ///
+ /// Gets or sets a value indicating whether detour should block gamepad input for game.
+ ///
+ /// Ideally, we would use
+ /// (ImGui.GetIO().ConfigFlags & ImGuiConfigFlags.NavEnableGamepad) > 0
+ /// but this has a race condition during load with the detour which sets up ImGui
+ /// and throws if our detour gets called before the other.
+ ///
+ internal bool NavEnableGamepad { get; set; }
+
///
/// Gets whether has been pressed.
///
/// Only true on first frame of the press.
+ /// If ImGuiConfigFlags.NavEnableGamepad is set, this is unreliable.
///
/// The button to check for.
/// 1 if pressed, 0 otherwise.
@@ -131,6 +146,7 @@ namespace Dalamud.Game.ClientState
/// Gets whether is being pressed.
///
/// True in intervals if button is held down.
+ /// If ImGuiConfigFlags.NavEnableGamepad is set, this is unreliable.
///
/// The button to check for.
/// 1 if still pressed during interval, 0 otherwise or in between intervals.
@@ -140,6 +156,7 @@ namespace Dalamud.Game.ClientState
/// Gets whether has been released.
///
/// Only true the frame after release.
+ /// If ImGuiConfigFlags.NavEnableGamepad is set, this is unreliable.
///
/// The button to check for.
/// 1 if released, 0 otherwise.
@@ -173,56 +190,68 @@ namespace Dalamud.Game.ClientState
private int GamepadPollDetour(IntPtr gamepadInput)
{
-#if DEBUG
- this.GamepadInput = gamepadInput;
-#endif
var original = this.gamepadPoll.Original(gamepadInput);
- var input = (GamepadInput*)gamepadInput;
- this.leftStickX = input->LeftStickX;
- this.leftStickY = input->LeftStickY;
- this.rightStickX = input->RightStickX;
- this.rightStickY = input->RightStickY;
- this.ButtonsRaw = input->ButtonsRaw;
- this.ButtonsPressed = input->ButtonsPressed;
- this.ButtonsReleased = input->ButtonsReleased;
- this.ButtonsRepeat = input->ButtonsRepeat;
-
- if ((ImGui.GetIO().ConfigFlags & ImGuiConfigFlags.NavEnableGamepad) > 0)
+ try
{
- input->LeftStickX = 0;
- input->LeftStickY = 0;
- input->RightStickX = 0;
- input->RightStickY = 0;
+#if DEBUG
+ this.GamepadInput = gamepadInput;
+#endif
+ var input = (GamepadInput*)gamepadInput;
+ this.leftStickX = input->LeftStickX;
+ this.leftStickY = input->LeftStickY;
+ this.rightStickX = input->RightStickX;
+ this.rightStickY = input->RightStickY;
+ this.ButtonsRaw = input->ButtonsRaw;
+ this.ButtonsPressed = input->ButtonsPressed;
+ this.ButtonsReleased = input->ButtonsReleased;
+ this.ButtonsRepeat = input->ButtonsRepeat;
- // NOTE (Chiv) Zeroing `ButtonsRaw` destroys `ButtonPressed`, `ButtonReleased`
- // and `ButtonRepeat` as the game uses the RAW input to determine those (apparently).
- // It does block, however, all input to the game.
- // Leaving `ButtonsRaw` as it is and only zeroing the other leaves e.g. long-hold L2/R2
- // and the digipad (in some situations, but thankfully not in menus) functional.
- // We can either:
- // (a) Explicitly only set L2/R2/Digipad to 0 (and destroy their `ButtonPressed` field) => Needs to be documented, or
- // (b) ignore it as so far it seems only a 'visual' error
- // (L2/R2 being held down activates CrossHotBar but activating an ability is impossible because of the others blocked input,
- // Digipad is ignored in menus but without any menu's one still switches target or party members, but cannot interact with them
- // because of the other blocked input)
- // `ButtonPressed` is pretty useful so we opt-in to (b).
- // This is debatable.
- // ImGui itself does not care either way as it uses the Raw values and does its own state handling.
- // input->ButtonsRaw &= (ushort)~GamepadButtons.L2;
- // input->ButtonsRaw &= (ushort)~GamepadButtons.R2;
- // input->ButtonsRaw &= (ushort)~GamepadButtons.DpadDown;
- // input->ButtonsRaw &= (ushort)~GamepadButtons.DpadLeft;
- // input->ButtonsRaw &= (ushort)~GamepadButtons.DpadUp;
- // input->ButtonsRaw &= (ushort)~GamepadButtons.DpadRight;
- input->ButtonsPressed = 0;
- input->ButtonsReleased = 0;
- input->ButtonsRepeat = 0;
- return 0;
+ if (this.NavEnableGamepad)
+ {
+ input->LeftStickX = 0;
+ input->LeftStickY = 0;
+ input->RightStickX = 0;
+ input->RightStickY = 0;
+
+ // NOTE (Chiv) Zeroing `ButtonsRaw` destroys `ButtonPressed`, `ButtonReleased`
+ // and `ButtonRepeat` as the game uses the RAW input to determine those (apparently).
+ // It does block, however, all input to the game.
+ // Leaving `ButtonsRaw` as it is and only zeroing the other leaves e.g. long-hold L2/R2
+ // and the digipad (in some situations, but thankfully not in menus) functional.
+ // We can either:
+ // (a) Explicitly only set L2/R2/Digipad to 0 (and destroy their `ButtonPressed` field) => Needs to be documented, or
+ // (b) ignore it as so far it seems only a 'visual' error
+ // (L2/R2 being held down activates CrossHotBar but activating an ability is impossible because of the others blocked input,
+ // Digipad is ignored in menus but without any menu's one still switches target or party members, but cannot interact with them
+ // because of the other blocked input)
+ // `ButtonPressed` is pretty useful but its hella confusing to the user, so we do (a) and advise plugins do not rely on
+ // `ButtonPressed` while ImGuiConfigFlags.NavEnableGamepad is set.
+ // This is debatable.
+ // ImGui itself does not care either way as it uses the Raw values and does its own state handling.
+ input->ButtonsRaw &= (ushort)~GamepadButtons.L2;
+ input->ButtonsRaw &= (ushort)~GamepadButtons.R2;
+ input->ButtonsRaw &= (ushort)~GamepadButtons.DpadDown;
+ input->ButtonsRaw &= (ushort)~GamepadButtons.DpadLeft;
+ input->ButtonsRaw &= (ushort)~GamepadButtons.DpadUp;
+ input->ButtonsRaw &= (ushort)~GamepadButtons.DpadRight;
+ input->ButtonsPressed = 0;
+ input->ButtonsReleased = 0;
+ input->ButtonsRepeat = 0;
+ return 0;
+ }
+
+ // NOTE (Chiv) Not so sure about the return value, does not seem to matter if we return the
+ // original, zero or do the work adjusting the bits.
+ return original;
}
+ catch (Exception e)
+ {
+ Log.Error(e, $"Gamepad Poll detour critical error! Gamepad navigation will not work!");
- // NOTE (Chiv) Not so sure about the return value, does not seem to matter if we return the
- // original, zero or do the work adjusting the bits.
- return original;
+ // NOTE (Chiv) Explicitly deactivate on error
+ ImGui.GetIO().ConfigFlags &= ~ImGuiConfigFlags.NavEnableGamepad;
+ return original;
+ }
}
private void Dispose(bool disposing)
diff --git a/Dalamud/Interface/InterfaceManager.cs b/Dalamud/Interface/InterfaceManager.cs
index dbf1556c8..3209fe9d9 100644
--- a/Dalamud/Interface/InterfaceManager.cs
+++ b/Dalamud/Interface/InterfaceManager.cs
@@ -304,6 +304,9 @@ namespace Dalamud.Interface
ImGui.GetIO().ConfigFlags |= ImGuiConfigFlags.NavEnableSetMousePos;
}
+ // NOTE (Chiv) Explicitly deactivate on dalamud boot
+ ImGui.GetIO().ConfigFlags &= ~ImGuiConfigFlags.NavEnableGamepad;
+
ImGuiHelpers.MainViewport = ImGui.GetMainViewport();
Log.Information("[IM] Scene & ImGui setup OK!");
@@ -484,7 +487,7 @@ namespace Dalamud.Interface
&& this.dalamud.ClientState.GamepadState.Pressed(GamepadButtons.L3) > 0)
{
ImGui.GetIO().ConfigFlags ^= ImGuiConfigFlags.NavEnableGamepad;
- this.dalamud.DalamudUi.TogglePluginInstaller();
+ this.dalamud.ClientState.GamepadState.NavEnableGamepad ^= true;
}
if (gamepadEnabled