From f8224fc322e1d27cfe0905cdf435c494c9a7c645 Mon Sep 17 00:00:00 2001
From: Roman Kitaev <802.11g@bk.ru>
Date: Sat, 18 Jul 2026 22:49:55 +0300
Subject: [PATCH] feat: add Reset Mouse Look keybind to recover the macOS
stuck-cursor freeze
---
.context/patterns.md | 1 +
CombatMode/Bindings.xml | 3 +++
CombatMode/Config/ConfigMouseLook.lua | 24 ++++++++++++++++++++++++
CombatMode/Core/FreeLookController.lua | 18 ++++++++++++++++++
4 files changed, 46 insertions(+)
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