Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .context/patterns.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.

Expand Down
3 changes: 3 additions & 0 deletions CombatMode/Bindings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
CombatMode_CursorModeKey(keystate)
end
</Binding>
<Binding name="Combat Mode - Reset Mouse Look" description="Recover the cursor if it gets stuck after switching windows (re-grabs and releases Mouse Look)" category="|A:::|a|TInterface\Addons\CombatMode\assets\cmtitle:22:95|t">
CombatMode_ResetMouseLook()
</Binding>
<Binding name="Combat Mode - Healing Radial" description="Hold to open Healing Radial for party targeting" header="Healing Radial" category="|A:::|a|TInterface\Addons\CombatMode\assets\cmtitle:22:95|t" runOnUp="true">
if keystate == "down" or keystate == "up" then
CombatMode_HealingRadialKey(keystate)
Expand Down
24 changes: 24 additions & 0 deletions CombatMode/Config/ConfigMouseLook.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
},

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can I get a screenshot showing how this new option sits on the config panel in-game, please? You might need to adjust the size/positioning of the other elements to make sure nothing is visually breaking/odd.

Might even be the case of adding a check to show this for the macOS client only, if possible, so we don’t run resync pulses on all clients by default.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Settings:
SCR-20260719-bjiw

Tooltip:
SCR-20260719-bjlq

Native Blizzard keybindings:
SCR-20260719-bjra

@sampconrad sampconrad Jul 19, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder what's causing it to break line like this...
image

UI scale, perhaps? 🤔
Ace3 and its peculiarities 😭

Here's what it's supposed to look like
image

Ideally, we'd fit that new keybind in a way that doesn't cause a vertical scroll on the page.

spacing = Spacing("full", 5.1),
pulseCursor = {
type = "toggle",
Expand Down
18 changes: 18 additions & 0 deletions CombatMode/Core/FreeLookController.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Comment thread
sampconrad marked this conversation as resolved.