diff --git a/.context/patterns.md b/.context/patterns.md index 3fc8138..1ada7ec 100644 --- a/.context/patterns.md +++ b/.context/patterns.md @@ -13,6 +13,7 @@ Suggested buckets (map to existing rules): ## CombatMode-specific reminders - **Mouselook / free-look / CVars:** free-look state machine lives in `Core/FreeLookController.lua`; **all addon-owned `SetCVar` writes route through** `Core/RuntimeCVarManager.lua` (`CM.SetCVar*` helpers). Modules should compute/decide values locally, then call the manager to perform the write. Keep enable/disable paths symmetric. +- **macOS stuck-cursor freeze:** a `MouselookStop()` issued while the window is backgrounded (auto-unlock from a watched frame during alt-tab, e.g. bags/LFG) leaves the OS mouse capture stuck — frozen cursor/camera on return while `IsMouselooking()` lies. There is no window-focus event and the background keeps rendering, so it can't be detected from `OnUpdate`; the `Combat Mode - Reset Mouse Look` keybind (`CombatMode_ResetMouseLook`, FreeLookController) re-grabs+releases (`MouselookStart()`→next-frame `MouselookStop()`) as manual recovery. - **Override bindings / secure buttons:** follow existing `SecureActionButtonTemplate` and `Core/BindingOverrides.lua` patterns. - **Vendored libs:** do not rewrite `CombatMode/Libs/**`; see `.cursor/rules/combatmode-vendored-libs.mdc`. diff --git a/CombatMode/Bindings.xml b/CombatMode/Bindings.xml index eee1053..071a72d 100644 --- a/CombatMode/Bindings.xml +++ b/CombatMode/Bindings.xml @@ -4,6 +4,9 @@ CombatMode_CursorModeKey(keystate) end + + CombatMode_ResetMouseLook() + if keystate == "down" or keystate == "up" then CombatMode_HealingRadialKey(keystate) diff --git a/CombatMode/Config/ConfigMouseLook.lua b/CombatMode/Config/ConfigMouseLook.lua index 4d11e60..e5beb3d 100644 --- a/CombatMode/Config/ConfigMouseLook.lua +++ b/CombatMode/Config/ConfigMouseLook.lua @@ -11,6 +11,7 @@ local Spacing, Header, Description = U.Spacing, U.Header, U.Description -- WoW API local GetBindingKey = _G.GetBindingKey local GetCurrentBindingSet = _G.GetCurrentBindingSet +local IsMacClient = _G.IsMacClient local ReloadUI = _G.ReloadUI local SaveBindings = _G.SaveBindings local SetBinding = _G.SetBinding @@ -188,6 +189,29 @@ CM.Config.MouseLookOptions = { return (GetBindingKey("INTERACTMOUSEOVER")) end, }, + resetMouseLook = { + type = "keybinding", + name = "|cffffd700Reset - |cffE52B50Mouse Look|r|r", + desc = "Recovers a stuck cursor/camera that can happen on |cffffd700macOS|r: when a panel (bags, |cffffd700LFG|r ready check, etc.) turns |cffE52B50Mouse Look|r off while |cffffd700WoW|r is in the background, the |cffffd700OS|r mouse capture is left stuck. On returning the camera is frozen and the cursor is gone.\n\nPress this to re-grab and release the camera, bringing the cursor back.\n\n|cff909090This can't be done automatically: the game exposes no window-focus event and keeps rendering while in the background, so the addon has no way to detect the frozen state from the inside. A manual keypress is the only reliable recovery.|r", + width = 1.15, + order = 5.05, + hidden = function() + return not IsMacClient() + end, + set = function(_, key) + CM.TryApplyBindingChange("reset mouse look keybinding", function() + local oldKey = (GetBindingKey("Combat Mode - Reset Mouse Look")) + if oldKey then + SetBinding(oldKey) + end + SetBinding(key, "Combat Mode - Reset Mouse Look") + SaveBindings(GetCurrentBindingSet()) + end) + end, + get = function() + return (GetBindingKey("Combat Mode - Reset Mouse Look")) + end, + }, spacing = Spacing("full", 5.1), pulseCursor = { type = "toggle", diff --git a/CombatMode/Core/FreeLookController.lua b/CombatMode/Core/FreeLookController.lua index be80955..877de30 100644 --- a/CombatMode/Core/FreeLookController.lua +++ b/CombatMode/Core/FreeLookController.lua @@ -218,3 +218,21 @@ function _G.CombatMode_CursorModeKey(keystate) FreeLookOverride = false end end + +-- Manual recovery for the macOS stuck-cursor freeze (a MouselookStop issued while the +-- window was backgrounded leaves the OS capture stuck). Re-grab and release through the +-- free-look state machine so the crosshair/tooltip/CVar side effects stay in sync rather +-- than trusting IsMouselooking(); OnUpdate re-locks afterward if free look should be active. +function _G.CombatMode_ResetMouseLook() + CM.LockFreeLook() + local function settle() + if CM.ShouldFreeLookBeOff() then + CM.UnlockFreeLook() + end + end + if C_Timer and C_Timer.After then + C_Timer.After(0, settle) + else + settle() + end +end